# How the Louvain Community Detection Algorithm Discovers Functional Modules in codebase-memory-mcp

> Discover functional modules in codebase-memory-mcp with the Louvain algorithm. It transforms your code into a call graph and uses Leiden optimization to group related call sites into modules.

- Repository: [Martin Vogel/codebase-memory-mcp](https://github.com/DeusData/codebase-memory-mcp)
- Tags: deep-dive
- Published: 2026-07-16

---

**The Louvain community detection algorithm in `codebase-memory-mcp` models your software as a call-graph where functions, methods, and classes are nodes, then applies a multi-level Leiden optimization to group tightly-coupled call-sites into cohesive functional modules.**

`codebase-memory-mcp` treats software architecture as a network analysis problem. By representing the codebase as a directed graph of **CALLS** relationships and running the Leiden algorithm (a modern, refined variant of Louvain), it automatically identifies logical functional modules without requiring manual annotation or predefined boundaries. The implementation is found in [`src/store/store.c`](https://github.com/DeusData/codebase-memory-mcp/blob/main/src/store/store.c) and exposed through the `cbm_louvain` and `cbm_leiden` APIs in [`src/store/store.h`](https://github.com/DeusData/codebase-memory-mcp/blob/main/src/store/store.h).

## Modeling Code as a Call Graph

Before detection begins, the system abstracts the codebase into a mathematical graph structure. In [`src/store/store.c`](https://github.com/DeusData/codebase-memory-mcp/blob/main/src/store/store.c), the `arch_clusters` routine constructs this graph by querying the underlying SQLite store.

### Nodes and Edge Relationships

The graph contains three types of **vertices**:

- **Function** nodes
- **Method** nodes  
- **Class** nodes

Edges represent directed **CALLS** relationships extracted from the `edges` table. According to the SQL queries in [`src/store/store.c`](https://github.com/DeusData/codebase-memory-mcp/blob/main/src/store/store.c) (lines 5290–5293 and 5439–5450), the algorithm selects only edges where both endpoints are code entities (functions, methods, or classes) and the relationship type is exactly `CALLS`. This produces a clean CSR-style edge list (`cbm_louvain_edge_t`) that encodes the directed dependencies between software components.

## The Leiden Algorithm Implementation

The core engine is `cbm_leiden`, a complete implementation of the Leiden algorithm following Traag et al. (2019). Unlike the classic Louvain method, Leiden guarantees that all communities are internally connected, producing more coherent modules. The algorithm proceeds through three repeating phases until modularity convergence.

### Phase 1: Local Moving

Each node is iteratively moved to a neighboring community if the move increases **modularity**—a measure of the density of edges inside communities compared to edges outside them. The algorithm evaluates the resolution-adjusted modularity gain for every possible move and selects the highest positive gain. This greedy optimization runs across all nodes repeatedly until no further improvements can be made.

### Phase 2: Refinement

After the local moving phase, the refinement step splits communities into smaller, well-connected sub-communities. This is the critical difference from classic Louvain: it ensures that the resulting functional modules are not just dense in connections, but internally coherent. A module cannot be fragmented into disconnected sub-components, which prevents the creation of illogical groupings in the software architecture.

### Phase 3: Aggregation

The algorithm contracts each discovered community into a single **super-node**, aggregating the edge weights between communities. The entire process—local moving, refinement, and aggregation—repeats recursively on this coarser graph until the modularity score converges and no further improvements are possible.

## From Graph to Functional Modules

The `arch_clusters` function orchestrates the end-to-end discovery pipeline in [`src/store/store.c`](https://github.com/DeusData/codebase-memory-mcp/blob/main/src/store/store.c) (starting around line 5152).

1. **Node selection**: Queries the database for all entities labeled `Function`, `Method`, or `Class`.
2. **Edge collection**: Retrieves `CALLS` edges where both source and target exist in the selected node set.
3. **Community detection**: Invokes `cbm_leiden` via the convenience wrapper `cbm_louvain` to partition the graph.
4. **Result processing**: Constructs `cbm_cluster_info_t` structures for each detected community.

The algorithm automatically adapts to graph size and density, requiring no prior knowledge of the codebase structure.

## Post-Processing and Cohesion Scoring

Raw community assignments are transformed into actionable architecture insights through quantitative analysis.

### Calculating Module Cohesion

For each detected community, `arch_clusters` computes a **cohesion** score defined as:

```

cohesion = internal_edges / (internal_edges + boundary_edges)

```

This metric, implemented in [`src/store/store.c`](https://github.com/DeusData/codebase-memory-mcp/blob/main/src/store/store.c) (lines 5422–5424), indicates how tightly-knit a functional module is. A cohesion score approaching 1.0 indicates a highly cohesive module with minimal external coupling, while lower scores suggest boundary-spanning concerns.

### Labeling and Filtering

The system generates human-readable labels by analyzing the most frequent package names within each community. It then filters the results to return the top **12** communities (by default) that contain at least two members, discarding singletons and noise.

Developers can influence the granularity of the results through the **resolution** parameter exposed in the `cbm_leiden` API. Higher resolution values produce more, smaller modules; lower values yield fewer, larger architectural components.

## Practical Usage Examples

You can invoke the functional module discovery through the C API or the command-line interface.

### C API Integration

```c
#include "store.h"

/* Open a store handle for your analyzed project */
cbm_store_t *store = cbm_store_open("my_project.db");
cbm_architecture_info_t arch;

/* Discover functional modules across the entire project */
int rc = cbm_store_get_architecture(
    store,                 // Store handle
    "my_project",          // Project name
    NULL,                  // Path filter (NULL = whole project)
    NULL, 0,               // Aspects (NULL = all)
    &arch);                // Output structure

if (rc == CBM_STORE_OK) {
    for (int i = 0; i < arch.cluster_count; ++i) {
        const cbm_cluster_info_t *c = &arch.clusters[i];
        printf("Module %d: %d members, cohesion %.2f, label: %s\n",
               c->id, c->members, c->cohesion, c->label);
    }
}

cbm_architecture_free(&arch);
cbm_store_close(store);

```

### Command-Line Interface

```bash

# Discover architecture via the PyPI package CLI

codebase-mcp-get-architecture \
    --project my_project \
    --store my_project.db \
    --aspects all

```

The CLI tool ultimately calls `cbm_store_get_architecture`, executing the same Leiden-based pipeline described above.

## Summary

- `codebase-memory-mcp` represents software as a **call-graph** where functions, methods, and classes are nodes connected by **CALLS** edges.
- The **Leiden algorithm** (accessible via `cbm_leiden` and `cbm_louvain` in [`src/store/store.h`](https://github.com/DeusData/codebase-memory-mcp/blob/main/src/store/store.h)) optimizes modularity through iterative local moving, refinement, and aggregation phases.
- The `arch_clusters` function in [`src/store/store.c`](https://github.com/DeusData/codebase-memory-mcp/blob/main/src/store/store.c) orchestrates graph construction, runs the detection algorithm, and filters results to return the top functional modules.
- Each module receives a **cohesion score** calculated from internal versus boundary edge counts, quantifying architectural coupling.
- The optional **resolution parameter** allows tuning of module granularity without re-analyzing the source code.

## Frequently Asked Questions

### What is the difference between Louvain and Leiden in this implementation?

**Leiden is the specific algorithm implemented in `codebase-memory-mcp`, serving as an improved successor to the classic Louvain method.** While both optimize modularity, Leiden adds a refinement phase that guarantees all communities are internally connected. This prevents the formation of fragmented modules where two disconnected subgraphs are incorrectly grouped together, resulting in more logically coherent functional modules in software architecture analysis.

### How does the algorithm handle large codebases?

**The multi-level aggregation strategy makes the implementation scalable to large graphs.** By contracting communities into super-nodes after each iteration, the algorithm reduces the problem size exponentially. The C implementation in [`src/store/store.c`](https://github.com/DeusData/codebase-memory-mcp/blob/main/src/store/store.c) uses efficient CSR-style edge lists and operates directly on the SQLite store, allowing it to process codebases with tens of thousands of functions without loading the entire graph into memory.

### What does the cohesion score represent?

**Cohesion measures the internal coupling strength of a discovered module relative to its external dependencies.** Calculated as `internal_edges / (internal_edges + boundary_edges)` in [`src/store/store.c`](https://github.com/DeusData/codebase-memory-mcp/blob/main/src/store/store.c) (lines 5422–5424), this score ranges from 0 to 1. A score near 1.0 indicates a highly cohesive module where most calls stay within the boundary, while lower scores suggest the module acts as a cross-cutting concern or integration layer.

### Can I adjust the granularity of the detected modules?

**Yes, through the resolution parameter exposed in the `cbm_leiden` API.** The resolution parameter acts as a scaling factor on the modularity calculation: values greater than 1.0 produce more, smaller communities (finer granularity), while values between 0 and 1.0 yield fewer, larger modules (coarser granularity). This allows you to tune the analysis to reveal microservices-level modules or high-level architectural layers without changing the underlying graph structure.