# How the Hybrid LSP Layer Resolves Types Across Python, TypeScript, and Go

> Discover how the Hybrid LSP layer resolves types across Python, TypeScript, and Go. It normalizes definitions, builds a global index, and uses language-specific resolvers for efficient symbol filtering.

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

---

**The Hybrid LSP layer in Codebase-Memory-MCP resolves types across multiple programming languages by normalizing definitions into a language-agnostic format, building a global module index, and dispatching to optimized language-specific resolvers that filter symbols based on import graphs and qualified names.**

The Codebase-Memory-MCP (CBM) project implements a sophisticated cross-language type resolution system through its Hybrid Language Server Protocol (LSP) layer. Unlike traditional LSP implementations that operate on single files, this hybrid architecture enables the resolution of type information across file boundaries and even between different programming languages. The core mechanism resides in the cross-file LSP pass (`cbm_pipeline_pass_lsp_cross`) located in [`src/pipeline/pass_lsp_cross.c`](https://github.com/DeusData/codebase-memory-mcp/blob/main/src/pipeline/pass_lsp_cross.c), which orchestrates a unified pipeline supporting Go, Python, TypeScript, Java, Kotlin, Rust, C/C++, C#, and PHP.

## The Eight-Stage Cross-File Resolution Pipeline

The Hybrid LSP layer processes every file through a standardized eight-stage pipeline that maintains language neutrality while leveraging optimized language-specific backends.

### Step 1: Collecting Project-Wide Definitions

The pass begins by gathering all definitions from every parsed file in the project. The function `cbm_pxc_collect_all_defs` converts each language-specific `CBMDefinition` into a generic `CBMLSPDef` structure that captures qualified names, short names, labels, receiver types, return types, and embedded types.

```c
CBMLSPDef *defs = cbm_pxc_collect_all_defs(cache, files, file_count,
                                          ctx->project_name, def_modules, &def_count);

```

This normalization allows the subsequent stages to treat Python classes, Go interfaces, and TypeScript types uniformly.

### Step 2: Building the Module-Definition Index

To enable fast lookups, the system constructs a `CBMModuleDefIndex` hash table that maps module qualified names (QNs) to their constituent definitions. For example, `github.com/example/pkg` or Java package namespaces point directly to the indices of relevant `CBMLSPDef` entries.

```c
CBMModuleDefIndex *mod_idx = cbm_pxc_build_module_def_index(defs, def_count);

```

This index allows the resolver to filter thousands of project-wide definitions down to only those relevant to a specific file in constant time.

### Step 3: Creating Language-Specific Shared Registries

For languages that support pre-built cross-registries (Go, Python, C, C#, and TypeScript), the pass creates a shared `CBMTypeRegistry` once per pipeline run. These compact, lookup-optimized structures store the full definition set in memory-efficient formats specific to each language's resolution patterns.

```c
// Example for Go (similar functions exist for other languages)
cbm_go_build_cross_registry(defs, def_count, &cross_registries);

```

### Step 4: Mapping Imports for Each File

Using the graph buffer (`gbuf`), the function `pxc_build_import_map` extracts **IMPORTS** edges to build a map of local import names to resolved module QNs. This tells the resolver exactly which external modules a file can legally reference.

```c
pxc_build_import_map(gbuf, ctx->project_name, file->rel_path,
                    &import_keys, &import_vals, &import_count);

```

### Step 5: Dispatching to Language-Specific Resolvers

The `cbm_pxc_dispatch_file` function serves as the central router. It first filters the global definition array to include only the current module and its imports using `cbm_pxc_filter_defs_for_file`. Then it chooses the resolution strategy:

- **Shared registry path**: For languages with cross-registries, it calls fast overlay resolvers like `cbm_go_fast_resolve_qualified_calls`
- **Per-file LSP path**: Falls back to `cbm_pxc_run_one` or `cbm_pxc_run_one_ts` for languages without shared registries

```c
cbm_pxc_dispatch_file(lang, cache[i], source, src_len, file->rel_path,
                      def_modules[i], &cross_registries, mod_idx,
                      defs, def_count, import_keys, import_vals, import_count,
                      NULL, NULL);

```

### Step 6: Language-Specific Cross-File Resolution

Each language implements a dedicated resolver that receives the filtered definitions, import map, source buffer, and AST. These functions walk the file's syntax tree and match symbols against the supplied `CBMLSPDef` array:

- **Go**: `cbm_run_go_lsp_cross` in [`internal/cbm/lsp/go_lsp.c`](https://github.com/DeusData/codebase-memory-mcp/blob/main/internal/cbm/lsp/go_lsp.c)
- **Python**: `cbm_run_py_lsp_cross` in [`internal/cbm/lsp/py_lsp.c`](https://github.com/DeusData/codebase-memory-mcp/blob/main/internal/cbm/lsp/py_lsp.c)
- **TypeScript/JavaScript**: `cbm_run_ts_lsp_cross` in [`internal/cbm/lsp/ts_lsp.c`](https://github.com/DeusData/codebase-memory-mcp/blob/main/internal/cbm/lsp/ts_lsp.c)

```c
void cbm_run_go_lsp_cross(CBMArena *arena,
                          const char *src, int src_len,
                          const char *module_qn,
                          CBMLSPDef *defs, int def_cnt,
                          const char **imp_names, const char **imp_qns,
                          int imp_cnt, TSTree *tree,
                          CBMResolvedCallArray *out) {
    // Parse AST and lookup symbols using the filtered defs and import map
}

```

### Step 7: Merging and Deduplicating Results

After resolution, `pxc_append_results` merges the language-specific outputs into the file's `resolved_calls` arena. A temporary hash-set eliminates duplicate *(caller → callee)* pairs that could arise from multiple import paths or re-exports.

```c
pxc_append_results(&result->arena, &result->resolved_calls, &out);

```

### Step 8: Resource Cleanup

Once all files are processed, the pass frees the module-definition index and global definition array. Shared registries remain cached for the pipeline duration, while Rust-specific resources like parsed [`Cargo.toml`](https://github.com/DeusData/codebase-memory-mcp/blob/main/Cargo.toml) manifests (handled via `cbm_cargo_parse`) are released if present.

## Cross-Language Type Resolution Mechanics

The "hybrid" aspect emerges from the system's ability to resolve types across language boundaries. When a Go file imports a Python package (such as via `cgo`-generated bindings), the Go resolver can still locate Python symbols because the global definition list already contains Python `CBMLSPDef` entries. The import map identifies which module QNs are visible, and the generic filtering logic works regardless of the source language.

### Special Handling for JVM Languages

Java and Kotlin derive their module qualified names from the containing directory rather than file stems. Helper functions like `pxc_module_is_dir` and `pxc_infer_jvm_namespace` ensure that the module QN matches the Java/Kotlin package hierarchy, enabling accurate cross-resolution between JVM languages and other systems.

### Rust Workspace Awareness

When a [`Cargo.toml`](https://github.com/DeusData/codebase-memory-mcp/blob/main/Cargo.toml) manifest is detected, the pass parses it using `cbm_cargo_parse` and stores a `CBMCargoManifest`. The Rust resolver (`cbm_run_rust_lsp_cross_with_manifest`) then uses this manifest to resolve crate-to-crate references across workspace boundaries.

## Key Implementation Files

The Hybrid LSP layer spans multiple source files:

- [`src/pipeline/pass_lsp_cross.c`](https://github.com/DeusData/codebase-memory-mcp/blob/main/src/pipeline/pass_lsp_cross.c) – Central driver (`cbm_pipeline_pass_lsp_cross`)
- [`src/pipeline/pass_lsp_cross.h`](https://github.com/DeusData/codebase-memory-mcp/blob/main/src/pipeline/pass_lsp_cross.h) – Public API for the cross pass
- [`src/pipeline/lsp_resolve.h`](https://github.com/DeusData/codebase-memory-mcp/blob/main/src/pipeline/lsp_resolve.h) – Shared types (`CBMLSPDef`, `CBMResolvedCallArray`)
- [`internal/cbm/lsp/go_lsp.c`](https://github.com/DeusData/codebase-memory-mcp/blob/main/internal/cbm/lsp/go_lsp.c) – Go resolver (`cbm_run_go_lsp_cross`)
- [`internal/cbm/lsp/py_lsp.c`](https://github.com/DeusData/codebase-memory-mcp/blob/main/internal/cbm/lsp/py_lsp.c) – Python resolver
- [`internal/cbm/lsp/ts_lsp.c`](https://github.com/DeusData/codebase-memory-mcp/blob/main/internal/cbm/lsp/ts_lsp.c) – TypeScript/JavaScript resolver
- [`internal/cbm/lsp/java_lsp.c`](https://github.com/DeusData/codebase-memory-mcp/blob/main/internal/cbm/lsp/java_lsp.c) – Java-specific fallback resolver
- [`internal/cbm/lsp/kotlin_lsp.c`](https://github.com/DeusData/codebase-memory-mcp/blob/main/internal/cbm/lsp/kotlin_lsp.c) – Kotlin resolver
- [`internal/cbm/lsp/rust_lsp.c`](https://github.com/DeusData/codebase-memory-mcp/blob/main/internal/cbm/lsp/rust_lsp.c) – Rust resolver with Cargo awareness
- [`internal/cbm/lsp/c_lsp.c`](https://github.com/DeusData/codebase-memory-mcp/blob/main/internal/cbm/lsp/c_lsp.c) – C/C++/CUDA resolver
- [`internal/cbm/lsp/cs_lsp.c`](https://github.com/DeusData/codebase-memory-mcp/blob/main/internal/cbm/lsp/cs_lsp.c) – C# resolver

- [`internal/cbm/lsp/php_lsp.c`](https://github.com/DeusData/codebase-memory-mcp/blob/main/internal/cbm/lsp/php_lsp.c) – PHP resolver

## Summary

- The **Hybrid LSP layer** normalizes all language definitions into a generic `CBMLSPDef` format, enabling a unified resolution pipeline.
- A **module-definition index** (`CBMModuleDefIndex`) provides O(1) lookups of symbols by module qualified name.
- **Language-specific shared registries** optimize performance for Go, Python, TypeScript, C, and C# by pre-building compact lookup structures.

- The **import map** and **filtering logic** ensure resolvers only see definitions reachable from the current file, even across language boundaries.
- Dedicated resolver functions in `internal/cbm/lsp/` handle language-specific AST walking while consuming the language-agnostic definition array.
- **Specialized handling** for JVM directory-based modules and Rust Cargo workspaces ensures accurate cross-language resolution in complex project structures.

## Frequently Asked Questions

### How does the Hybrid LSP layer handle cross-language imports?

The Hybrid LSP layer handles cross-language imports by maintaining a global `CBMLSPDef` array that contains normalized definitions from all languages. When resolving a file, the system builds an import map that associates local import aliases with module qualified names (QNs). The dispatcher then filters the global definition list to include only definitions from the current module and its imported modules, regardless of language. This allows a Go resolver to find Python symbols when the import map indicates a cross-language dependency exists.

### What is the difference between the shared registry and per-file LSP resolution?

The **shared registry** path creates a compact, pre-built `CBMTypeRegistry` once per pipeline run for languages like Go, Python, and TypeScript. This registry enables fast "overlay" resolution via functions like `cbm_go_fast_resolve_qualified_calls`. The **per-file LSP** path (`cbm_pxc_run_one`) processes definitions on a per-file basis without pre-built structures, serving as a fallback for languages without shared registry implementations or when memory constraints prevent caching. The dispatcher automatically selects the fastest available path based on the target language.

### How are JVM languages like Java and Kotlin handled differently?

JVM languages use directory-based module qualified names rather than file-based names. The `pxc_module_is_dir` helper and `pxc_infer_jvm_namespace` functions extract the package hierarchy from the directory structure to construct accurate module QNs that match Java and Kotlin package declarations. This ensures that when Java files import Kotlin classes (or vice versa), the module index correctly associates the definitions with their namespace paths, enabling accurate cross-resolution between JVM languages.

### Can the Hybrid LSP layer resolve types in Rust workspaces?

Yes, the Hybrid LSP layer includes specific support for Rust workspaces through Cargo manifest parsing. When a [`Cargo.toml`](https://github.com/DeusData/codebase-memory-mcp/blob/main/Cargo.toml) file is present, the pass invokes `cbm_cargo_parse` to create a `CBMCargoManifest`. The Rust resolver (`cbm_run_rust_lsp_cross_with_manifest`) uses this manifest to understand crate boundaries and dependencies, allowing it to resolve types across multiple crates within a workspace. The manifest is stored during the pipeline run and freed during the cleanup phase after all files are processed.