Monorepo Analysis
Monorepo Analysis
Section titled “Monorepo Analysis”Arxo has native support for monorepo architectures, automatically detecting workspace configurations and analyzing package boundaries, blast radius, and cross-package coupling.
Supported Monorepo Tools
Section titled “Supported Monorepo Tools”Arxo automatically detects and parses:
| Tool | Detection | Package Discovery |
|---|---|---|
| pnpm | pnpm-workspace.yaml | Workspace packages |
| npm workspaces | package.json workspaces field | Workspace packages |
| Yarn workspaces | package.json workspaces field | Workspace packages |
| Turborepo | turbo.json + package manager | Pipeline + packages |
| Cargo | Cargo.toml workspace | Workspace members |
| Nx | Detected via npm/pnpm + nx.json | Project configuration |
Detection Order
Section titled “Detection Order”- pnpm-workspace.yaml (highest priority)
- npm/yarn workspaces in root
package.json - turbo.json (marks workspace as Turborepo)
- Cargo.toml with
[workspace] - Single package (fallback)
Quick Start
Section titled “Quick Start”Analyze Any Monorepo
Section titled “Analyze Any Monorepo”# Automatic workspace detectioncd your-monorepoarxo analyze --preset ciArxo will:
- Detect workspace type (pnpm, Turbo, Cargo, etc.)
- Load all package metadata
- Analyze cross-package dependencies
- Compute package-level metrics
Enable Monorepo Metric
Section titled “Enable Monorepo Metric”For detailed monorepo-specific analysis:
arxo analyze --metric monorepoOr add to .arxo.yaml:
metrics: - id: monorepo enabled: trueMonorepo Metric
Section titled “Monorepo Metric”The monorepo metric provides package-level insights:
What It Measures
Section titled “What It Measures”- Policy Violations - Direct and transitive package-dependency rule violations
- Blast Radius - How many packages are affected when one changes
- Build Alignment - Drift between static package deps and task-graph deps
- Affected-Set Quality - Precision/recall/F1 against historical co-change (git)
- Dependency Governance + Ownership - Version/workspace-protocol drift and bus-factor risk
Key Values
Section titled “Key Values”monorepo.inventory.package_count: 24 # Total packagesmonorepo.policy.edge_violations_count: 8 # Direct policy edge violationsmonorepo.policy.transitive_violations_count: 3 # Transitive policy violationsmonorepo.policy.compliance_ratio: 0.88 # 88% of evaluated policy edges complymonorepo.structure.blast_radius.max: 15 # Worst-case blast radiusmonorepo.structure.blast_radius.avg: 3.2 # Average packages affected per changemonorepo.build.missing_dependency_edges_count: 4 # Static deps missing in task graphWorkspace Detection
Section titled “Workspace Detection”pnpm Workspaces
Section titled “pnpm Workspaces”pnpm-workspace.yaml:
packages: - 'packages/*' - 'apps/*'Arxo reads this file and:
- Discovers all packages matching the globs
- Parses each
package.jsonfor dependencies - Maps cross-package dependencies
npm/Yarn Workspaces
Section titled “npm/Yarn Workspaces”package.json:
{ "workspaces": [ "packages/*", "apps/*" ]}Same behavior as pnpm.
Turborepo
Section titled “Turborepo”turbo.json:
{ "pipeline": { "build": { "dependsOn": ["^build"] }, "test": { "dependsOn": ["build"] } }}Arxo reads:
- Package manager config (pnpm/npm/yarn)
- Turborepo pipeline for task dependencies
Cargo Workspaces
Section titled “Cargo Workspaces”Cargo.toml:
[workspace]members = [ "crates/*", "packages/cli", "packages/engine"]Arxo reads:
- Workspace members from
Cargo.toml - Dependencies from each crate’s
Cargo.toml
Configuration
Section titled “Configuration”Group by Package
Section titled “Group by Package”Analyze at the package level:
data: import_graph: group_by: folder group_depth: 2 # packages/feature-auth → one nodeFor structure like:
packages/ feature-auth/ feature-cart/ shared-ui/apps/ web/ mobile/group_depth: 2 groups by packages/feature-auth, apps/web, etc.
Exclude Node Modules
Section titled “Exclude Node Modules”data: import_graph: exclude: - "**/node_modules/**" - "**/dist/**" - "**/build/**"Package-Specific Exclusions
Section titled “Package-Specific Exclusions”data: import_graph: exclude: - "packages/legacy/**" # Exclude old packages - "apps/storybook/**" # Exclude non-production appsCommon Patterns
Section titled “Common Patterns”Pattern 1: Nx Monorepo
Section titled “Pattern 1: Nx Monorepo”Structure:
apps/ web/ mobile/libs/ feature-auth/ feature-cart/ shared/ui/ shared/utils/Config:
data: import_graph: group_by: folder group_depth: 2 # Group by feature/app exclude: - "**/node_modules/**" - "**/*.spec.ts"
metrics: - id: monorepo - id: scc - id: propagation_cost
policy: invariants: # No cycles between features - metric: scc.max_cycle_size op: "==" value: 0
# No direct policy violations - metric: monorepo.policy.edge_violations_count op: "==" value: 0
# No transitive policy violations - metric: monorepo.policy.transitive_violations_count op: "==" value: 0
# Limit blast radius - metric: monorepo.structure.blast_radius.max op: "<=" value: 10Pattern 2: Turborepo with pnpm
Section titled “Pattern 2: Turborepo with pnpm”Structure:
apps/ docs/ web/ api/packages/ ui/ config/ tsconfig/Config:
data: import_graph: group_by: folder group_depth: 2 exclude: - "**/node_modules/**" - "**/.turbo/**"
metrics: - id: monorepo - id: propagation_cost - id: centrality
policy: invariants: # No direct policy violations - metric: monorepo.policy.edge_violations_count op: "==" value: 0
# Low coupling - metric: propagation_cost.system.ratio op: "<=" value: 0.15
# No transitive policy violations - metric: monorepo.policy.transitive_violations_count op: "==" value: 0Pattern 3: Cargo Workspace
Section titled “Pattern 3: Cargo Workspace”Structure:
crates/ cli/ engine/ types/ mcp/Config:
data: import_graph: group_by: folder group_depth: 2 exclude: - "**/target/**" - "**/tests/**"
metrics: - id: monorepo - id: scc - id: package_metrics
policy: invariants: # No cycles - metric: scc.max_cycle_size op: "==" value: 0
# Stable core crates - metric: package_metrics.module.instability.max op: "<=" value: 0.5Policy Edge Violations
Section titled “Policy Edge Violations”What Are Policy Edge Violations?
Section titled “What Are Policy Edge Violations?”When a package dependency violates a configured monorepo policy rule (for example, apps/* must not depend on apps/* directly):
Bad (disallowed cross-package dependency):
import { mobileConfig } from '@myapp/mobile';Good (allowed shared boundary):
import { mobileConfig } from '@myapp/shared-config';Why They Matter
Section titled “Why They Matter”- Prevents architecture drift - Keeps boundaries explicit and enforceable
- Reduces hidden coupling - Avoids accidental forbidden dependencies
- Improves change safety - Clear rules reduce cross-team surprises
- Makes policy auditable - Violations are measurable in CI
Detecting Violations
Section titled “Detecting Violations”arxo analyze --metric monorepo --format json --output report.json
# Extract violationsjq '.results[] | select(.id=="monorepo") | .findings[] | select(.rule_id=="monorepo.policy.edge_violation")' report.jsonExample Output:
{ "rule_id": "monorepo.policy.edge_violation", "finding_type": "api_boundary_violation", "title": "Policy edge violation: apps/web -> apps/mobile", "description": "Direct package dependency violates configured policy."}Fixing Violations
Section titled “Fixing Violations”-
Move shared dependency to an allowed package:
packages/shared-config/src/index.ts export const mobileConfig = { /* ... */ }; -
Update import to allowed boundary:
apps/web/src/app.ts import { mobileConfig } from '@myapp/shared-config'; -
If intentional, update monorepo policy config (allow/deny rules) and document the exception.
Policy Example
Section titled “Policy Example”policy: invariants: - metric: monorepo.policy.edge_violations_count op: "==" value: 0 message: "No direct policy edge violations"Blast Radius Analysis
Section titled “Blast Radius Analysis”What Is Blast Radius?
Section titled “What Is Blast Radius?”The number of packages affected when one package changes.
Example: Changing shared/ui affects:
feature-auth(importsButton)feature-cart(importsInput)app-web(imports both features)
Blast radius = 3 packages
Why It Matters
Section titled “Why It Matters”- Change cost - High blast radius = expensive changes
- Testing scope - More packages to test
- Deployment risk - More packages to deploy
- Coordination - More teams affected
Interpreting Values
Section titled “Interpreting Values”monorepo.structure.blast_radius.max: 15 # Worst package affects 15 othersmonorepo.structure.blast_radius.avg: 3.2 # Average package affects 3.2 othersThresholds:
max < 10— Good (manageable)max 10-20— Warning (high coupling)max > 20— Critical (god package)
Reducing Blast Radius
Section titled “Reducing Blast Radius”Strategy 1: Split Large Packages
Before:
shared/ui/ (100 components → used by 20 packages)After:
shared/ui-core/ (10 components → used by 20 packages)shared/ui-forms/ (20 components → used by 5 packages)shared/ui-charts/ (10 components → used by 3 packages)Strategy 2: Introduce Adapter Layers
feature-auth → shared/ui-adapter → shared/ui-corefeature-cart → shared/ui-adapter → shared/ui-coreChanges to shared/ui-core only affect shared/ui-adapter, not all features.
Strategy 3: Dependency Injection
// Before: direct dependencyimport { Logger } from '@myapp/shared-logger';
// After: injected dependencyexport function createService(logger: Logger) { // ...}Policy Example
Section titled “Policy Example”policy: invariants: - metric: monorepo.structure.blast_radius.max op: "<=" value: 10 message: "No package should affect more than 10 others"
- metric: monorepo.structure.blast_radius.avg op: "<=" value: 5.0 message: "Average blast radius should be ≤ 5"Layering Violations
Section titled “Layering Violations”Standard Monorepo Layers
Section titled “Standard Monorepo Layers”apps/ (Top layer - applications) ↓ can import fromfeatures/ (Middle layer - business features) ↓ can import fromshared/ (Bottom layer - utilities)Rules:
- Apps can import from features and shared
- Features can import from shared
- Shared cannot import from features or apps
- Apps cannot import from other apps
- Features cannot import from other features (without explicit rules)
Detected Violations
Section titled “Detected Violations”1. Apps-to-Apps
Section titled “1. Apps-to-Apps”Bad:
import { config } from '@myapp/mobile'; // ❌Why: Apps should be independent deployable units.
Fix: Extract shared config to shared/config.
2. Shared-to-App
Section titled “2. Shared-to-App”Bad:
import { theme } from '@myapp/web'; // ❌Why: Shared libraries should be reusable across apps.
Fix: Move theme to shared/theme.
3. Feature-to-Feature (Optional Enforcement)
Section titled “3. Feature-to-Feature (Optional Enforcement)”Some monorepos allow feature-to-feature dependencies, others enforce strict isolation.
Strict:
import { user } from '@myapp/feature-auth'; // ❌ ViolationAllowed:
import { User } from '@myapp/shared-types'; // ✓ OKPolicy Example
Section titled “Policy Example”policy: invariants: - metric: monorepo.policy.edge_violations_count op: "==" value: 0 message: "No direct layer-policy violations"
- metric: monorepo.policy.transitive_violations_count op: "==" value: 0 message: "No transitive layer-policy violations"Per-Package Analysis
Section titled “Per-Package Analysis”Analyze Single Package
Section titled “Analyze Single Package”# Analyze one packagearxo analyze --path packages/feature-authAnalyze Changed Packages
Section titled “Analyze Changed Packages”In CI, analyze only packages affected by PR:
# Get changed filesCHANGED=$(git diff --name-only origin/main)
# Analyze packages containing changed filesarxo analyze --only-changed origin/mainPackage-Specific Config
Section titled “Package-Specific Config”Create .arxo.yaml in package root:
data: import_graph: exclude: - "**/*.test.ts"
policy: invariants: - metric: scc.max_cycle_size op: "==" value: 0CI Integration
Section titled “CI Integration”GitHub Actions (Monorepo)
Section titled “GitHub Actions (Monorepo)”name: Monorepo Analysis
on: pull_request: branches: [main]
jobs: analyze: runs-on: ubuntu-latest
steps: - uses: actions/checkout@v4 with: fetch-depth: 0 # For git history metrics
- name: Install Arxo run: | curl -sL "https://github.com/arxohq/arxo/releases/latest/download/arxo-linux-x86_64.tar.gz" | tar xz chmod +x arxo && sudo mv arxo /usr/local/bin/
- name: Analyze Workspace run: | arxo analyze --metric monorepo --format json --output report.json
- name: Check Policy run: | # Fail if violations found VIOLATIONS=$(jq '.violations | length' report.json) if [ "$VIOLATIONS" -gt 0 ]; then echo "❌ $VIOLATIONS policy violations detected" jq '.violations' report.json exit 1 fi
- name: Upload Report uses: actions/upload-artifact@v3 if: always() with: name: monorepo-report path: report.jsonTurborepo Task
Section titled “Turborepo Task”Add to turbo.json:
{ "pipeline": { "analyze": { "dependsOn": ["^build"], "outputs": ["report.json"] } }}Run:
turbo run analyzepnpm Script
Section titled “pnpm Script”Add to root package.json:
{ "scripts": { "analyze": "arxo analyze --metric monorepo", "analyze:ci": "arxo analyze --preset ci --fail-fast" }}Run:
pnpm analyzeBest Practices
Section titled “Best Practices”1. Start with Detection
Section titled “1. Start with Detection”# Verify workspace detectionarxo analyze --metric monorepo --format json | jq '.metadata.workspace'Should show:
{ "workspace_type": "pnpm", "package_count": 24, "root_path": "/path/to/repo"}2. Define Clear Boundaries
Section titled “2. Define Clear Boundaries”Package structure:
packages/ @myapp/feature-auth/ (Public API: src/index.ts) @myapp/feature-cart/ (Public API: src/index.ts) @myapp/shared-ui/ (Public API: src/index.ts)Enforce public APIs:
policy: invariants: - metric: monorepo.policy.edge_violations_count op: "==" value: 03. Document Layers
Section titled “3. Document Layers”Add README.md to workspace root:
# Monorepo Structure
## Layers
- `apps/` - Applications (web, mobile, docs)- `features/` - Business features (auth, cart, checkout)- `shared/` - Shared utilities (ui, utils, types)
## Rules
- Apps can import: features, shared- Features can import: shared- Shared can import: nothing (except other shared)4. Track Metrics Over Time
Section titled “4. Track Metrics Over Time”# Generate baselinearxo analyze --metric monorepo --format snapshot --output baseline.yamlgit add baseline.yamlgit commit -m "Add monorepo baseline"
# Compare in CIarxo analyze --baseline baseline.yaml5. Use Presets
Section titled “5. Use Presets”For monorepo CI:
arxo analyze --preset ci --metric monorepoFor full monorepo audit:
arxo analyze --preset coupling --metric monorepoTroubleshooting
Section titled “Troubleshooting”Workspace Not Detected
Section titled “Workspace Not Detected”Symptoms: package_count: 0 or workspace_type: "SinglePackage"
Solutions:
-
Check workspace file exists:
Terminal window ls pnpm-workspace.yaml # pnpmcat package.json | jq .workspaces # npm/yarncat Cargo.toml | grep workspace # cargo -
Check file format:
Terminal window # pnpm-workspace.yamlpackages:- 'packages/*' # ✓ Correct# NOT:# packages: packages/* # ❌ Wrong -
Check package paths:
Terminal window # Verify packages exist at specified pathsls packages/ # Should show package directories
Policy Edge Violations Not Detected
Section titled “Policy Edge Violations Not Detected”Cause: No monorepo policy rules configured, so there is nothing to violate.
Check: Is monorepo.policy configured with allow/deny rules?
arxo analyze --metric monorepo --format json --output report.jsonjq '.results[] | select(.id=="monorepo") | .data[] | select(.key | startswith("monorepo.policy."))' report.jsonBlast Radius Too High
Section titled “Blast Radius Too High”Cause: God package (e.g., shared/utils imported everywhere)
Solution: Split god package:
Before:
shared/utils/ src/ string.ts array.ts date.ts api.ts storage.tsAfter:
shared/utils-string/shared/utils-array/shared/utils-date/shared/api/shared/storage/Performance Issues
Section titled “Performance Issues”For large monorepos (>100 packages):
-
Exclude build artifacts:
import_graph:exclude:- "**/node_modules/**"- "**/dist/**"- "**/.turbo/**" -
Use caching:
Terminal window # First run: slowarxo analyze --metric monorepo# Subsequent runs: fast (cached)arxo analyze --metric monorepo -
Analyze changed packages only:
Terminal window arxo analyze --only-changed origin/main
Examples
Section titled “Examples”Nx Monorepo with Strict Boundaries
Section titled “Nx Monorepo with Strict Boundaries”data: import_graph: group_by: folder group_depth: 2
metrics: - id: monorepo - id: scc - id: propagation_cost - id: centrality
policy: invariants: # Structural - metric: scc.max_cycle_size op: "==" value: 0 message: "No circular dependencies"
# Monorepo boundaries - metric: monorepo.policy.edge_violations_count op: "==" value: 0 message: "Use public APIs only"
- metric: monorepo.policy.compliance_ratio op: ">=" value: 0.99 message: "Maintain near-perfect policy compliance"
- metric: monorepo.policy.transitive_violations_count op: "==" value: 0 message: "Shared cannot depend on apps"
# Coupling - metric: monorepo.structure.blast_radius.max op: "<=" value: 12 message: "Limit blast radius"
- metric: propagation_cost.system.ratio op: "<=" value: 0.20 message: "Keep coupling manageable"
# Hub detection - metric: centrality.module.max_fan_out op: "<=" value: 15 message: "No god packages"Turborepo with Pragmatic Rules
Section titled “Turborepo with Pragmatic Rules”data: import_graph: group_by: folder group_depth: 2 exclude: - "**/node_modules/**" - "**/.turbo/**"
metrics: - id: monorepo - id: scc
policy: invariants: # Allow small cycles (legacy) - metric: scc.max_cycle_size op: "<=" value: 3
# Enforce transitive layer boundaries - metric: monorepo.policy.transitive_violations_count op: "==" value: 0
# Allow some direct policy violations (gradual cleanup) - metric: monorepo.policy.edge_violations_count op: "<=" value: 10 message: "Reduce violations over time"
# Track blast radius but don't fail - metric: monorepo.structure.blast_radius.max op: "<=" value: 20Next Steps
Section titled “Next Steps”- CI Integration - Integrate monorepo checks in CI
- Monorepo Governance Metric - Current key contract and findings
- Policy Examples - More policy patterns
- Presets - Use coupling preset for monorepos
- Propagation Cost - Measure cross-package coupling
- Package Metrics - Stability and abstractness