Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
683e8a3
feat(perl-lsp): add perl_lsp.h header with PerlLSPContext
shanemccarron-maker Jun 13, 2026
7341328
feat(perl-lsp): add perl_lsp.c skeleton with no-op phases A/B/C
shanemccarron-maker Jun 13, 2026
9222280
feat(perl-lsp): add stub perl_stdlib_data.c
shanemccarron-maker Jun 13, 2026
e767216
feat(perl-lsp): wire dispatch, unity build, and Makefile
shanemccarron-maker Jun 13, 2026
1a358d7
feat(perl-lsp): seed perlfunc core builtins in perl_stdlib_data.c
shanemccarron-maker Jun 13, 2026
43d66e0
feat(perl-lsp): seed common CPAN OOP modules in perl_stdlib_data.c
shanemccarron-maker Jun 13, 2026
8af197a
feat(perl-lsp): implement core semantic resolution (package/sub/metho…
shanemccarron-maker Jun 13, 2026
0d378e9
fix(perl-lsp): surface resolved method/static calls as graph CALLS edges
shanemccarron-maker Jun 13, 2026
55227ac
fix(perl-lsp): collect @ISA parents from parenthesized RHS siblings
shanemccarron-maker Jun 13, 2026
f85f401
test(perl-lsp): add Perl LSP resolution suite mirroring test_php_lsp.c
shanemccarron-maker Jun 13, 2026
8385a11
fix(perl-lsp): address QA round 1
shanemccarron-maker Jun 14, 2026
cab2ff3
fix(perl-lsp): split qualified calls on last "::" so multi-level pack…
shanemccarron-maker Jun 16, 2026
0dc2d43
chore(planning): drop bundled VBW planning summary from PR
shanemccarron-maker Jun 29, 2026
ef1e221
fix(perl-lsp): address QA round 2 follow-ups
shanemccarron-maker Jul 6, 2026
123afbe
docs(perl-lsp): document Perl in the Hybrid LSP section
shanemccarron-maker Jul 6, 2026
ff68247
fix(perl-lsp): guard pathologically nested Perl before tree-sitter parse
shanemccarron-maker Jul 6, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion Makefile.cbm
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,8 @@ TEST_CS_LSP_SRCS = tests/test_cs_lsp.c

TEST_CS_LSP_BENCH_SRCS = tests/test_cs_lsp_bench.c

TEST_PERL_LSP_SRCS = tests/test_perl_lsp.c

TEST_SCOPE_SRCS = tests/test_scope.c

TEST_TYPE_REP_SRCS = tests/test_type_rep.c
Expand Down Expand Up @@ -468,7 +470,7 @@ TEST_REPRO_SRCS = \
tests/repro/repro_lsp_java_cs.c \
tests/repro/repro_lsp_kt_php_rust.c

ALL_TEST_SRCS =$(TEST_FOUNDATION_SRCS) $(TEST_EXTRACTION_SRCS) $(TEST_STORE_SRCS) $(TEST_CYPHER_SRCS) $(TEST_MCP_SRCS) $(TEST_DISCOVER_SRCS) $(TEST_GRAPH_BUFFER_SRCS) $(TEST_PIPELINE_SRCS) $(TEST_WATCHER_SRCS) $(TEST_LZ4_SRCS) $(TEST_ZSTD_SRCS) $(TEST_ARTIFACT_SRCS) $(TEST_SQLITE_WRITER_SRCS) $(TEST_GO_LSP_SRCS) $(TEST_C_LSP_SRCS) $(TEST_PHP_LSP_SRCS) $(TEST_CS_LSP_SRCS) $(TEST_CS_LSP_BENCH_SRCS) $(TEST_SCOPE_SRCS) $(TEST_TYPE_REP_SRCS) $(TEST_PY_LSP_SRCS) $(TEST_PY_LSP_BENCH_SRCS) $(TEST_PY_LSP_STRESS_SRCS) $(TEST_PY_LSP_SCALE_SRCS) $(TEST_TS_LSP_SRCS) $(TEST_JAVA_LSP_SRCS) $(TEST_KOTLIN_LSP_SRCS) $(TEST_RUST_LSP_SRCS) $(TEST_TRACES_SRCS) $(TEST_CLI_SRCS) $(TEST_MEM_SRCS) $(TEST_UI_SRCS) $(TEST_HTTPD_SRCS) $(TEST_SECURITY_SRCS) $(TEST_YAML_SRCS) $(TEST_SEMANTIC_SRCS) $(TEST_AST_PROFILE_SRCS) $(TEST_SLAB_ALLOC_SRCS) $(TEST_SIMHASH_SRCS) $(TEST_STACK_OVERFLOW_SRCS) $(TEST_INTEGRATION_SRCS)
ALL_TEST_SRCS =$(TEST_FOUNDATION_SRCS) $(TEST_EXTRACTION_SRCS) $(TEST_STORE_SRCS) $(TEST_CYPHER_SRCS) $(TEST_MCP_SRCS) $(TEST_DISCOVER_SRCS) $(TEST_GRAPH_BUFFER_SRCS) $(TEST_PIPELINE_SRCS) $(TEST_WATCHER_SRCS) $(TEST_LZ4_SRCS) $(TEST_ZSTD_SRCS) $(TEST_ARTIFACT_SRCS) $(TEST_SQLITE_WRITER_SRCS) $(TEST_GO_LSP_SRCS) $(TEST_C_LSP_SRCS) $(TEST_PHP_LSP_SRCS) $(TEST_CS_LSP_SRCS) $(TEST_CS_LSP_BENCH_SRCS) $(TEST_PERL_LSP_SRCS) $(TEST_SCOPE_SRCS) $(TEST_TYPE_REP_SRCS) $(TEST_PY_LSP_SRCS) $(TEST_PY_LSP_BENCH_SRCS) $(TEST_PY_LSP_STRESS_SRCS) $(TEST_PY_LSP_SCALE_SRCS) $(TEST_TS_LSP_SRCS) $(TEST_JAVA_LSP_SRCS) $(TEST_KOTLIN_LSP_SRCS) $(TEST_RUST_LSP_SRCS) $(TEST_TRACES_SRCS) $(TEST_CLI_SRCS) $(TEST_MEM_SRCS) $(TEST_UI_SRCS) $(TEST_HTTPD_SRCS) $(TEST_SECURITY_SRCS) $(TEST_YAML_SRCS) $(TEST_SEMANTIC_SRCS) $(TEST_AST_PROFILE_SRCS) $(TEST_SLAB_ALLOC_SRCS) $(TEST_SIMHASH_SRCS) $(TEST_STACK_OVERFLOW_SRCS) $(TEST_INTEGRATION_SRCS)


# ── Build directories ────────────────────────────────────────────
Expand Down
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
[![CI](https://img.shields.io/github/actions/workflow/status/DeusData/codebase-memory-mcp/dry-run.yml?label=CI)](https://github.com/DeusData/codebase-memory-mcp/actions/workflows/dry-run.yml)
[![Tests](https://img.shields.io/badge/tests-5604_passing-brightgreen)](https://github.com/DeusData/codebase-memory-mcp)
[![Languages](https://img.shields.io/badge/languages-158-orange)](https://github.com/DeusData/codebase-memory-mcp)
[![Hybrid LSP](https://img.shields.io/badge/Hybrid_LSP-9_languages-blue)](#hybrid-lsp)
[![Hybrid LSP](https://img.shields.io/badge/Hybrid_LSP-10_languages-blue)](#hybrid-lsp)
[![Agents](https://img.shields.io/badge/agents-11-purple)](https://github.com/DeusData/codebase-memory-mcp)
[![Pure C](https://img.shields.io/badge/pure_C-zero_dependencies-blue)](https://github.com/DeusData/codebase-memory-mcp)
[![Platform](https://img.shields.io/badge/macOS_%7C_Linux_%7C_Windows-supported-lightgrey)](https://github.com/DeusData/codebase-memory-mcp/releases/latest)
Expand All @@ -16,7 +16,7 @@

**The fastest and most efficient code intelligence engine for AI coding agents.** Full-indexes an average repository in milliseconds, the Linux kernel (28M LOC, 75K files) in 3 minutes. Answers structural queries in under 1ms. Ships as a single static binary for macOS, Linux, and Windows — download, run `install`, done.

High-quality parsing through [tree-sitter](https://tree-sitter.github.io/tree-sitter/) AST analysis across all 158 languages, enhanced with [**Hybrid LSP** semantic type resolution](#hybrid-lsp) for Python, TypeScript / JavaScript / JSX / TSX, PHP, C#, Go, C, C++, Java, Kotlin, and Rust — producing a persistent knowledge graph of functions, classes, call chains, HTTP routes, and cross-service links. 14 MCP tools. Zero dependencies. Plug and play across 11 coding agents.
High-quality parsing through [tree-sitter](https://tree-sitter.github.io/tree-sitter/) AST analysis across all 158 languages, enhanced with [**Hybrid LSP** semantic type resolution](#hybrid-lsp) for Python, TypeScript / JavaScript / JSX / TSX, PHP, C#, Go, C, C++, Java, Kotlin, Rust, and Perl — producing a persistent knowledge graph of functions, classes, call chains, HTTP routes, and cross-service links. 14 MCP tools. Zero dependencies. Plug and play across 11 coding agents.

> **Research** — The design and benchmarks behind this project are described in the preprint [*Codebase-Memory: Tree-Sitter-Based Knowledge Graphs for LLM Code Exploration via MCP*](https://arxiv.org/abs/2603.27277) (arXiv:2603.27277). Evaluated across 31 real-world repositories: 83% answer quality, 10× fewer tokens, 2.1× fewer tool calls vs. file-by-file exploration.

Expand Down Expand Up @@ -179,7 +179,7 @@ Removes all agent configs, skills, hooks, and instructions. Does not remove the
- **158 vendored tree-sitter grammars** compiled into the binary
- **Generic package / module resolution** — bare specifiers like `@myorg/pkg`, `github.com/foo/bar`, `use my_crate::foo` resolved via manifest scanning (`package.json`, `go.mod`, `Cargo.toml`, `pyproject.toml`, `composer.json`, `pubspec.yaml`, `pom.xml`, `build.gradle`, `mix.exs`, `*.gemspec`)
- **Infrastructure-as-code indexing** — Dockerfiles, Kubernetes manifests, Kustomize overlays as graph nodes
- **[Hybrid LSP semantic type resolution](#hybrid-lsp)** for Python, TypeScript / JavaScript / JSX / TSX, PHP, C#, Go, C, C++, Java, Kotlin, and Rust — a lightweight C implementation of language type-resolution algorithms, structurally inspired by and compatible with major language servers including tsserver / typescript-go, pyright, gopls, Roslyn, Eclipse JDT, and rust-analyzer (parameter binding, return-type inference, generic substitution, JSX component dispatch, JSDoc inference for plain JS files, namespace + trait + late-static-binding resolution for PHP, file-scoped namespaces + records + LINQ method syntax for C#, class-hierarchy + overload + lambda resolution for Java, extension-function + scope-function resolution for Kotlin, trait-method + UFCS resolution for Rust)
- **[Hybrid LSP semantic type resolution](#hybrid-lsp)** for Python, TypeScript / JavaScript / JSX / TSX, PHP, C#, Go, C, C++, Java, Kotlin, Rust, and Perl — a lightweight C implementation of language type-resolution algorithms, structurally inspired by and compatible with major language servers including tsserver / typescript-go, pyright, gopls, Roslyn, Eclipse JDT, and rust-analyzer (parameter binding, return-type inference, generic substitution, JSX component dispatch, JSDoc inference for plain JS files, namespace + trait + late-static-binding resolution for PHP, file-scoped namespaces + records + LINQ method syntax for C#, class-hierarchy + overload + lambda resolution for Java, extension-function + scope-function resolution for Kotlin, trait-method + UFCS resolution for Rust)
- **RAM-first pipeline**: LZ4 compression, in-memory SQLite, single dump at end. Memory released after.

### Distribution & operation
Expand Down Expand Up @@ -548,6 +548,7 @@ codebase-memory-mcp ships a **lightweight C implementation of language type-reso
| **Java** *(new in v0.8.0)* | imports (single-type, on-demand, static), class hierarchies with `this` / `super` dispatch, generics, annotations, overload matching by arity and parameter types, lambdas / method references bound to functional interfaces, field-type inference, common JDK stdlib |
| **Kotlin** *(new in v0.8.0)* | imports + same-package resolution, classes / objects / companion objects, extension functions, data classes, nullable-type unwrapping, scope functions (`let` / `apply` / `run` / `also` / `with`), infix calls, common stdlib |
| **Rust** *(new in v0.8.0)* | `use` declarations + module paths, `impl` blocks and trait methods, struct fields, generics with trait bounds, operator-trait desugaring, derive-macro method synthesis, UFCS static paths, common std prelude |
| **Perl** | packages + `@ISA` / `use parent` / `use base` inheritance with method-resolution-order dispatch, `SUPER::` calls, Exporter (`use Foo qw(...)`) import maps, `bless` / `ref($class)\|\|$class` self-type inference, qualified `Pkg::sub` static calls, curated perlfunc + CPAN OOP stdlib; unresolved receivers emit no edge (zero-edge guarantee) |

**Two-layer architecture:**

Expand Down
52 changes: 52 additions & 0 deletions internal/cbm/cbm.c
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#include "lsp/go_lsp.h"
#include "lsp/c_lsp.h"
#include "lsp/php_lsp.h"
#include "lsp/perl_lsp.h"
#include "lsp/py_lsp.h"
#include "lsp/ts_lsp.h"
#include "lsp/cs_lsp.h"
Expand Down Expand Up @@ -683,6 +684,43 @@ static void cbm_test_fault_inject(const char *rel_path) {
}
}

/* Pre-parse nesting guard for pathologically nested input. tree-sitter's GLR
* parser recurses once per nesting level inside stack_node_add_link
* (vendored ts_runtime/src/stack.c) while merging ambiguous parse-stack heads.
* The Perl grammar is genuinely ambiguous for `f(...)` (function call vs.
* bareword), so a deeply nested call chain `f(f(f(...)))` drives that recursion
* as deep as the nesting and overflows a small (1 MB Windows) stack *during the
* parse* — before any of the LSP walk-depth guards can fire. Unambiguous
* grammars (C/Java/Python) keep a single stack head and don't hit this, which is
* why only Perl crashed on the Windows/ARM CI runners.
*
* This is a workaround: the proper fix is bounding the GLR stack-merge recursion
* inside the vendored tree-sitter runtime, tracked upstream as #913. Remove this
* guard once that lands.
*
* cbm_source_nesting_exceeds scans the raw bytes for the maximum bracket-nesting
* depth and returns true as soon as it passes the cap (early-exit, O(n)). Real
* source never nests brackets this deep, so a file that does is skipped as a
* parse error (zero edges — graceful degradation, never a crash). Brackets in
* strings/comments are counted too: the only consequence of a false positive is
* skipping one absurd file, so string-awareness is not worth the cost. */
#define CBM_PERL_MAX_PARSE_NESTING 128

static bool cbm_source_nesting_exceeds(const char *source, int source_len, int cap) {
int depth = 0;
for (int i = 0; i < source_len; i++) {
char c = source[i];
if (c == '(' || c == '[' || c == '{') {
if (++depth > cap) {
return true;
}
} else if ((c == ')' || c == ']' || c == '}') && depth > 0) {
depth--;
}
}
return false;
}

static CBMFileResult *cbm_extract_file_impl(const char *source, int source_len,
CBMLanguage language, const char *project,
const char *rel_path, int64_t timeout_micros,
Expand Down Expand Up @@ -746,6 +784,17 @@ static CBMFileResult *cbm_extract_file_impl(const char *source, int source_len,
return result;
}

// Skip pathologically nested Perl before tree-sitter's recursive GLR stack
// merge overflows a small stack during the parse (see
// cbm_source_nesting_exceeds). Scoped to Perl: its ambiguous call grammar is
// the only one that drives that recursion to the nesting depth.
if (language == CBM_LANG_PERL &&
cbm_source_nesting_exceeds(source, source_len, CBM_PERL_MAX_PARSE_NESTING)) {
result->has_error = true;
result->error_msg = cbm_arena_strdup(a, "perl source nesting too deep; skipped");
return result;
}

// Get thread-local parser (reused across files on same thread)
TSParser *parser = get_thread_parser(ts_lang, language);
if (!parser) {
Expand Down Expand Up @@ -836,6 +885,9 @@ static CBMFileResult *cbm_extract_file_impl(const char *source, int source_len,
if (language == CBM_LANG_PHP) {
cbm_run_php_lsp(a, result, source, source_len, root);
}
if (language == CBM_LANG_PERL) {
cbm_run_perl_lsp(a, result, source, source_len, root);
}
if (language == CBM_LANG_PYTHON) {
cbm_run_py_lsp(a, result, source, source_len, root);
}
Expand Down
Loading
Loading