graph-analysis · diff
git:20260821.ff81a24 to git:20260913.7e7bfd7
9 added, 0 removed. Audit A to A.
---
name: graph-analysis
description: "Query the project as a dependency graph to assess the blast radius of a change, find dead or risky elements, and evaluate module health. Use when planning a refactor, judging what a change will break, or looking for unused documents."
---
# Graph Analysis: Impact Assessment & Architecture Evaluation
Use this skill when assessing the impact of a change, identifying risky elements, evaluating module health, or planning a structural refactor. The graph tables model the entire project as a dependency graph and expose topology metrics on top of it.
## When to Read This Skill
- Before modifying an entity or microflow that might be widely used
- Before moving elements between modules
- When assessing architectural health (coupling, cohesion, god nodes)
- When planning to split a module or extract a bounded context
- When identifying dead code to remove
---
## Setup: Two-Step Refresh
Two separate commands are needed. The full catalog must exist before community detection runs.
```bash
# Step 1: Build the full catalog (activities, widgets, cross-references)
# Skip if already fresh — check .mxcli/catalog.db mtime
./mxcli -p app.mpr -c "REFRESH CATALOG FULL"
# Step 2: Run graph algorithms (community detection, centrality, layers, cycles)
./mxcli -p app.mpr -c "REFRESH CATALOG COMMUNITIES"
```
**What each step populates:**
| Step | Tables populated |
|------|-----------------|
| `REFRESH CATALOG FULL` | `GRAPH_GOD_NODES`, `GRAPH_MODULE_COUPLING`, `GRAPH_MODULE_COHESION`, `GRAPH_DEAD_ASSETS`, `GRAPH_ENTITY_HOTSPOTS`, `GRAPH_MODULE_DEPENDENCIES`, `GRAPH_REFKIND_DISTRIBUTION` |
| `REFRESH CATALOG COMMUNITIES` | `COMMUNITIES`, `COMMUNITY_SUMMARY`, `GRAPH_CYCLES`, `GRAPH_LAYERS`, `GRAPH_CENTRALITY`, `GRAPH_INTEGRATION_SURFACE` |
+ **An empty table from the second row is ambiguous unless you check.** Step 2 is
+ not a build mode — it augments whatever the catalog is, so `Build mode: full`
+ says nothing about whether it ran, and before mendixlabs/mxcli#1060 an un-run
+ pass and a genuinely cycle-free project produced the same `0 rows`. A query now
+ warns `requires refresh catalog communities (not run for this catalog)` when the
+ pass is missing, and `SHOW CATALOG STATUS` has a `Graph analysis:` line. **No
+ warning plus no rows is a real answer**; treat anything else as "not computed
+ yet", not as "clean".
+
`REFRESH CATALOG COMMUNITIES` with a resolution modifier:
- `REFRESH CATALOG COMMUNITIES` — default resolution (balanced granularity)
- `REFRESH CATALOG COMMUNITIES resolution 0.6` — coarser clusters (fewer, larger communities; good for monolith-to-multi-app planning)
- `REFRESH CATALOG COMMUNITIES resolution 2.0` — finer clusters (more, smaller communities; good for identifying sub-modules within a large module)
---
## Use Case 1: Pre-Change Impact Assessment
Run before modifying any entity, microflow, or association. Takes under a minute.
```sql
-- Is this element a god node? How many things depend on it?
SELECT Asset, ObjectType, InDegree, OutDegree, Degree
FROM CATALOG.GRAPH_GOD_NODES
WHERE Asset = 'MyModule.MyEntity'
-- What layer is it in? (0 = leaf/data, higher = orchestration)
SELECT AssetName, Layer
FROM CATALOG.GRAPH_LAYERS
WHERE AssetName = 'MyModule.MyMicroflow'
-- Is it a bridge? Would breaking it disconnect subsystems?
SELECT AssetName, PageRank, Betweenness
FROM CATALOG.GRAPH_CENTRALITY
WHERE AssetName = 'MyModule.MyMicroflow'
-- Which community does it belong to? What else is in that cluster?
SELECT c.AssetName, c.CommunityId, cs.Label
FROM CATALOG.COMMUNITIES c
JOIN CATALOG.COMMUNITY_SUMMARY cs ON c.CommunityId = cs.CommunityId
WHERE c.AssetName = 'MyModule.MyEntity'
```
**Interpretation:**
- `InDegree > 15`: many callers — changing this will require wide testing
- `Betweenness > 100`: this element is a bridge; removing or changing its signature will disconnect subsystems
- High layer number (8+): orchestration-level code; changes propagate downward
- Layer 0: pure data entity or leaf microflow — changes affect only direct callers
---
## Use Case 2: Dead Code Identification
```sql
-- All unreferenced elements project-wide
SELECT QualifiedName, ObjectType, ModuleName
FROM CATALOG.GRAPH_DEAD_ASSETS
WHERE ModuleName NOT IN ('System', 'Atlas_Core', 'Atlas_Web_Content',
'Atlas_NativeMobile_Content', 'WorkflowCommons', 'CommunityCommons',
'NanoflowCommons', 'NativeMobileResources', 'WebActions', 'DataWidgets')
ORDER BY ModuleName, ObjectType
```
**False positives to exclude before deleting:**
| Pattern | Why it's a false positive |
|---------|--------------------------|
| Microflows named `*_Tool` | Registered as LLM agent tools dynamically — never called directly in code |
| `ApplicationCore.ASU` and `ASU_*` | Configured in project settings, not referenced in MDL |
| Pages used as workflow user task pages | Workflow activities reference pages via model GUID, not code edges |
| Pages used as navigation home pages | Navigation assignments are not code edges |
| `IVK_*` microflows | Called by external systems via published REST/web services |
| `SCH_*` microflows | Called by scheduled events — no code edge |
Safe to delete: pages, snippets, and microflows with no inbound edges that do **not** match any of the patterns above, and that are not referenced in navigation (check `SHOW NAVIGATION`).
---
## Use Case 3: Module Health (Cohesion & Coupling)
```sql
-- Cohesion per module: low % = module does too many things / should be split
SELECT ModuleName, IntraEdges, InterEdges, CohesionPct
FROM CATALOG.GRAPH_MODULE_COHESION
ORDER BY CohesionPct ASC
-- Cross-module coupling: find unexpected entanglements
-- Filter to custom modules by replacing the IN list
SELECT SourceModule, TargetModule, Edges, RefKinds
FROM CATALOG.GRAPH_MODULE_COUPLING
WHERE SourceModule IN ('MyModule1', 'MyModule2', 'MyModule3')
AND TargetModule IN ('MyModule1', 'MyModule2', 'MyModule3')
ORDER BY Edges DESC
-- Dependency direction breakdown: what kinds of references cross module boundaries?
SELECT SourceModule, TargetModule, RefKind, Edges
FROM CATALOG.GRAPH_MODULE_DEPENDENCIES
WHERE SourceModule = 'MyModule'
ORDER BY Edges DESC
```
**Interpreting CohesionPct:**
| Range | Meaning |
|-------|---------|
| 70–100% | Self-contained module — good |
| 40–70% | Moderate coupling to other modules — watch if it drops further |
| < 40% | Low cohesion — module is a cross-cutter or should be split; investigate whether this is intentional (e.g. an orchestration/startup module) |
**Coupling RefKinds to watch:**
- `retrieve`, `datasource`, `create`, `change`: data access across module boundaries — should go through a service microflow in the owning module
- `call`: microflow calls across modules — acceptable if intentional API
- `generalize`: entity inheritance across modules — hard to refactor, flag as a blocker for splits
- `layout`: purely structural, always acceptable
---
## Use Case 4: Community Analysis (Bounded Context Validation)
```sql
-- What communities exist, how large are they, which modules do they span?
SELECT CommunityId, Label, Size, Modules
FROM CATALOG.COMMUNITY_SUMMARY
ORDER BY Size DESC
-- Which community does a specific element belong to?
SELECT c.AssetName, c.CommunityId, cs.Label, cs.Size
FROM CATALOG.COMMUNITIES c
JOIN CATALOG.COMMUNITY_SUMMARY cs ON c.CommunityId = cs.CommunityId
WHERE c.AssetName LIKE 'MyModule.%'
ORDER BY c.CommunityId
-- Elements from your module that landed in a foreign community
-- (indicates tight coupling to that community's domain)
SELECT c.AssetName, cs.Label AS CommunityLabel
FROM CATALOG.COMMUNITIES c
JOIN CATALOG.COMMUNITY_SUMMARY cs ON c.CommunityId = cs.CommunityId
WHERE c.AssetName LIKE 'MyModule.%'
AND cs.Label != 'MyModule'
ORDER BY cs.Label
```
**What to look for:**
- If your module's elements are split across multiple communities, the module spans more than one bounded context — consider splitting it.
- If elements from module A consistently land in module B's community, module A has a hidden dependency on B's domain.
- Two communities both labeled the same marketplace module (e.g. `AgentCommons` appears twice) signals a natural internal split within that module.
---
## Use Case 5: Finding Bridges (Centrality)
```sql
-- Top microflows by betweenness in your custom modules
-- High betweenness = bridge; breaking it disconnects subsystems
SELECT AssetName, PageRank, Betweenness
FROM CATALOG.GRAPH_CENTRALITY
WHERE AssetName LIKE 'MyModule.%'
OR AssetName LIKE 'AnotherModule.%'
ORDER BY Betweenness DESC
LIMIT 20
-- God nodes by PageRank (most influential, not just most-referenced)
SELECT Asset, ObjectType, ModuleName, InDegree, OutDegree, Degree
FROM CATALOG.GRAPH_GOD_NODES
WHERE ModuleName IN ('MyModule1', 'MyModule2')
ORDER BY Degree DESC
LIMIT 20
```
**Betweenness vs Degree:**
- `Degree` (InDegree + OutDegree): raw connection count. High degree = popular or knows-too-much.
- `Betweenness`: fraction of shortest paths in the graph that pass through this node. High betweenness = bridge between clusters. A node can have low degree but high betweenness (a narrow connector between two large clusters).
- `PageRank`: recursive importance — being depended on by important things matters more than being depended on by many small things.
Use betweenness to find microflows where an interface change would require coordinating across many callers simultaneously.
---
## Use Case 6: Entity Hotspots
```sql
-- Entities used by the most microflows, and across the most modules
SELECT Entity, UsedByFlows, AcrossModules
FROM CATALOG.GRAPH_ENTITY_HOTSPOTS
WHERE Entity LIKE 'MyModule.%'
ORDER BY UsedByFlows DESC
```
An entity appearing in `AcrossModules` with 3+ module names is a candidate for an explicit service layer — instead of every module reading it directly, route access through a `DS_` or `SUB_` microflow in the owning module.
---
## Use Case 7: Dependency Cycles
```sql
-- All cycles: self-references (size 1) and mutual references (size 2+)
SELECT AssetName, ModuleName, CycleId, CycleSize
FROM CATALOG.GRAPH_CYCLES
ORDER BY CycleSize DESC, CycleId
```
**Size 1**: self-referencing element (e.g. a workflow that references its own context entity — usually harmless).
**Size 2**: mutual reference between two elements — e.g. workflow A calls workflow B which triggers workflow A. Investigate before assuming it's intentional; these can cause infinite loops at runtime.
**Size 3+**: a dependency ring — must be broken before the module can be split or properly layered.
---
## Use Case 8: Monolith-to-Multi-App Planning
For splitting a project into separate Mendix apps connected via OData, REST, or Business Events:
```bash
# Use coarse resolution for candidate app groupings
./mxcli -p app.mpr -c "REFRESH CATALOG COMMUNITIES resolution 0.6"
```
```sql
-- Each cross-community edge becomes an integration contract after a split
-- RefKind tells you what protocol would be needed
SELECT * FROM CATALOG.GRAPH_INTEGRATION_SURFACE
ORDER BY SourceCommunity, TargetCommunity
-- 'generalize' edges are blockers — entity inheritance cannot cross app boundaries
SELECT * FROM CATALOG.GRAPH_INTEGRATION_SURFACE
WHERE RefKind = 'generalize'
```
**RefKind to integration protocol mapping** (matches the `Mechanism` column the view computes):
| RefKind | `Mechanism` label | Protocol after split |
|---------|-------------------|---------------------|
| `associate` | `OData / shared entity` | OData / shared entity |
| `retrieve` | `OData read` | OData (read) |
| `create`, `change` | `event / REST write` | REST or Business Events |
| `call` | `REST (published microflow)` | REST (synchronous) or Business Events (async) |
| `generalize` | `BLOCKER: inheritance across boundary` | **Blocker** — cannot cross app boundaries; must restructure |
| anything else (e.g. `datasource`, `layout`) | `review` | Inspect manually — layouts are UI-only and duplicate per app; others case-by-case |
---
## Topological Layer Reference
`GRAPH_LAYERS` assigns each element a layer number via topological sort (Kahn's algorithm on the dependency DAG):
| Layer | Typical elements |
|-------|-----------------|
| 0 | Entities, associations — pure data, no outgoing calls |
| 1 | Leaf microflows: tool callbacks, event handlers, simple helpers |
| 2–3 | Business logic: workflow triggers, process microflows |
| 4–6 | Orchestration: agent calls, composites |
| 7+ | Startup / configuration: ASU chain, import pipelines |
Elements at the same layer can be parallelised safely. A microflow calling something at a higher layer than itself indicates a layering violation (upward dependency).
---
## Quick Reference: Which Table Answers What
| Question | Table |
|----------|-------|
| How many things depend on X? | `GRAPH_GOD_NODES` (InDegree) |
| Is X a bridge between subsystems? | `GRAPH_CENTRALITY` (Betweenness) |
| What layer is X in? | `GRAPH_LAYERS` |
| What community (bounded context) is X in? | `COMMUNITIES` + `COMMUNITY_SUMMARY` |
| Which modules are tightly coupled? | `GRAPH_MODULE_COUPLING` |
| Is module M self-contained? | `GRAPH_MODULE_COHESION` |
| What is unused / dead? | `GRAPH_DEAD_ASSETS` |
| Are there circular dependencies? | `GRAPH_CYCLES` |
| Which entities are hotspots? | `GRAPH_ENTITY_HOTSPOTS` |
| What contracts would a split require? | `GRAPH_INTEGRATION_SURFACE` |
| What reference types cross module boundaries? | `GRAPH_MODULE_DEPENDENCIES` |
---
## Related Skills
- [assess-quality](../assess-quality/SKILL.md) — Full quality assessment including lint, report scores, and manual review guidelines
- [organize-project](../organize-project/SKILL.md) — MOVE command and folder structure
- [write-lint-rules](../write-lint-rules/SKILL.md) — Encoding a layering policy as an enforced Starlark rule
- [odata-data-sharing](../odata-data-sharing/SKILL.md) — Publishing entities for cross-app access