handle_get_architecture called cbm_store_get_schema, whose per-label/per-type property-key discovery runs json_each() over EVERY node and edge row (~10s of the 23s on a 4.8M-node kernel graph) — yet the handler only renders label/type counts. Added cbm_store_get_schema_counts (same impl, with_props=false) and switched the handler to it. The full get_graph_schema tool is unchanged. A partial expression index on is_entry_point was tried and reverted: json_extract in an index WHERE aborts store open on rows with malformed properties JSON (pre-existing in older DBs; see next commit).
resolve_name_lookup walked the full by_name candidate array (reachability check + score per candidate, re-done per file via the per-file caches) for every cache-miss name. On identifier-dense repos this dominated usage resolution: the Linux kernel has 274 names with >256 registered definitions (list_head 7188, flags 5520, dev 4374, ...) and resolve_usages burned 987s CPU. candidate_count_penalty already floors confidence to ~3/count (<=0.006 at 256), so these matches were noise edges. Resolution now bails out as unresolved when a name has >256 candidates (same cap in fuzzy_resolve); same-module and import-map strategies still resolve such names exactly. Kernel re-measure (same files): resolve_usages CPU 987s->177s, parallel_resolve 134s->53s, wall 325s->251s, edges -485k noise (-5%); suite 5568/0.
get_architecture's arch_boundaries looked up the package of every CALLS edge endpoint with a linear scan over all Function/Method/Class nodes (lookup_pkg over parallel arrays) — O(edges * nodes). On the Linux kernel graph (~1.4M defs, ~1.4M CALLS edges) the boundaries aspect spun >10 minutes at 100% CPU; latent until C call extraction was fixed (the kernel previously produced almost no CALLS edges). The node query now orders by id and lookup_pkg binary-searches: 80k nodes/160k edges 16.6s -> 207ms in the regression test; kernel-graph get_architecture(all) >10min -> 23s. Reproduce-first: arch_boundaries_no_quadratic_scan (red 16566ms pre-fix vs 8000ms bound, green 207ms post-fix).
C++/CUDA out-of-line method definitions (Foo::bar in a .cpp; the class body lives declaration-only in a header) were recorded as free Functions with no link to their class. A new helper cpp_out_of_line_parent_class() detects a qualified_identifier declarator on a function_definition and resolves the immediate enclosing class (descending nested scopes ns::Foo::bar to the direct parent); extract_func_def then promotes the def to label Method with parent_class set to the class QN so DEFINES_METHOD edges resolve. Distilled (reimplemented) from PR #428. Reproduce-first: cpp_out_of_line_method_issue428 (single-level out-of-line defs; the nested-namespace path isn't isolation-testable because tree-sitter-cpp parses a synthetic doubly-qualified def as an ERROR node, so it is exercised by real codebases).
The C++ LSP passes a class's declared template-param names (e.g. template<class T, class Alloc = ...>) alongside an instance's fewer template args (vector<int>). The substitution loop is bounded by type_params, so a param matching beyond the supplied args indexed type_args[i] past the array — reading adjacent arena memory (a type-name string byte) as a CBMType* that c_eval_expr_type later dereferenced -> SEGV (ASan: stack-buffer-overflow in cbm_type_substitute via c_eval_expr_type). Deterministic, all platforms; distinct from the #424 allocator crash. Fix at the single chokepoint: bound the args length by the (NULL-terminated) param count and gate every type_args[i] access by it, so no caller can over-read regardless of language. Reproduce-first: typerep_substitute_short_args_no_oob_issue427.
packaging/glama -> pkg/glama so all packaging lives under pkg/. Update the
_smoke.yml job command and the Dockerfile/comment references to the new path.
verify.sh is path-relative (dirname $0), so its logic is unchanged.
mimalloc bumped to v3.3.2 (MI_MALLOC_VERSION 30302), which adds static-CRT malloc override on Windows. Makefile: define the real MI_MALLOC_OVERRIDE=1 ONLY on MinGW (enables the static-CRT override entry points — the generic Windows fix for #424); on Unix keep static-link-order override (defining MI_MALLOC_OVERRIDE there compiles alloc-override.c's forwarding defs and crashes macOS with 'mi_free: invalid pointer' since system libs keep the system allocator). The old -DMI_OVERRIDE=1 was never read by mimalloc source — kept as a prod marker. smoke-test.sh: hard-fail on any 'mimalloc: error' in stderr. Regenerated scripts/vendored-checksums.txt for the new tree (security-vendored.sh passes; no dangerous calls in vendored mimalloc).
The standard linux release binary dynamically links glibc 2.38+ and
GLIBCXX_3.4.32, so it fails to start on Debian 11, Ubuntu 20.04/22.04,
RHEL/Rocky 8/9, Amazon Linux 2, etc. — yet install.sh, the npm and PyPI
wrappers, and the binary's own self-update all fetched it by default,
contradicting the "single static binary" promise.
Point every linux install + self-update path at the fully-static
"-portable" asset (gcc -static), which has no glibc floor. macOS/Windows
are unaffected and unchanged.
- install.sh, pkg/npm/install.js, pkg/pypi _cli.py: select -portable on linux
- src/cli/cli.c: self-update download URL AND checksum archive name both use
-portable on linux (they must match or the update fails checksum verify)
- scripts/smoke-test.sh: assert linux self-update targets the -portable asset
- scripts/ci/check-glibc-compat.sh: new guard — runs the binary inside
debian:bullseye (glibc 2.31) and asserts it starts
- _smoke.yml: run the guard on the portable binary in smoke-linux-portable
Reproduced: standard binary -> "GLIBC_2.38 not found" on glibc 2.31;
portable binary runs cleanly.
The standard linux release asset dynamically links glibc 2.38 / libstdc++
(GLIBCXX_3.4.32) and failed to start on debian:bookworm-slim (glibc 2.36).
The -portable asset is fully static (gcc -static); the image now builds and
passes the MCP initialize + tools/list handshake (15 tools).
- glama.json claims maintainership on glama.ai.
- packaging/glama/Dockerfile wraps the static Linux binary in a minimal
image so Glama can launch the stdio server and run its MCP introspection
checks (which power the directory score badge). The tool needs no Docker
to run; this image is only for the directory integration. Fetches the
latest release binary, arch-aware via TARGETARCH.
- packaging/glama/verify.sh builds the image and asserts the server answers
initialize + tools/list with no project indexed.
- _smoke.yml runs verify.sh as a non-gating job (continue-on-error) so the
integration is guarded against drift without ever blocking a release.
- Add `mcpName` to the npm package and an `mcp-name:` marker to the PyPI
README so the registry can verify package ownership for both.
- Rewrite server.json to reference the npm + PyPI packages instead of
five per-platform mcpb tarballs. The old form was pinned to a stale
0.6.1 with hardcoded SHA-256s that needed manual updates every release;
the package form only needs a version sync. Also trims the description
to the registry's 100-character limit.
- Add a publish-mcp-registry job: downloads mcp-publisher, authenticates
via GitHub Actions OIDC (no token, no device flow), syncs server.json's
version to the just-published npm package, and publishes. It runs after
npm/PyPI but does not gate the release un-draft, so a registry outage
never blocks shipping and the job can be retried on its own.
Defense-in-depth follow-up to the tree-sitter bind: route sqlite3 and libgit2
allocations through mimalloc explicitly so a correct binary never depends on the
fragile MI_OVERRIDE symbol override (where the Windows static-MinGW link can
resolve a library's malloc to mimalloc but its free to the CRT, corrupting the
heap). Prod-only, guarded by the existing CBM_BIND_TS_ALLOCATOR; the CRT+ASan
test build compiles these as no-ops and stays unchanged.
New cbm_alloc_init() consolidates all three binds behind a static idempotency
guard. main() calls it as its very first statement — before cbm_mcp_server_new
opens the in-memory store — because SQLITE_CONFIG_MALLOC must run before the
first sqlite3_open*/sqlite3_initialize, else sqlite3_config returns SQLITE_MISUSE
silently and the bind is ignored (the return code is asserted). cbm_init() also
calls cbm_alloc_init() so pipeline-pass entry points get the binds too; the
ts_set_allocator bind moved out of cbm_init into cbm_alloc_init.
sqlite3: a static sqlite3_mem_methods backed by mi_malloc/mi_free/mi_realloc,
xSize via mi_usable_size, xRoundup to an 8-byte boundary, xInit returning
SQLITE_OK, xShutdown a no-op (int-sized sqlite signatures wrapped with size_t
casts). libgit2: a static git_allocator (gmalloc/grealloc/gfree → mi_*) installed
via git_libgit2_opts(GIT_OPT_SET_ALLOCATOR). The allocator is set BEFORE any
git_libgit2_init: git_allocator_global_init keeps a custom allocator if one is
already set (git__allocator.gmalloc != git_failalloc_malloc), the pre-init global
is the fail-allocator, and there is no allocator reset on git_libgit2_shutdown —
so binding once at startup survives pass_githistory's per-call init/shutdown
pairs. libgit2 code is additionally guarded by HAVE_LIBGIT2.
Test build 5564/0 ASan-clean; prod build clean with CBM_BIND_TS_ALLOCATOR=1; the
HAVE_LIBGIT2 branch type-checked against the libgit2 sys/alloc.h struct shape;
lint-ci clean.
The C++ heap-corruption crash only manifests in the production binary (MI_OVERRIDE=1), so the smoke job (which runs the prod binary) is where it reproduces — most reliably on Windows. A ~10k-line templated header exercises the parse-churn path; indexing it must succeed.
Root cause (corrected from the report): not a tree-sitter null-subtree sentinel — 0xffffffffffffffff is a freed/heap-corrupted pointer. The vendored ts runtime allocates via its overridable ts_current_malloc/free; under the prod build's MI_OVERRIDE=1, the Windows static-MinGW link (--allow-multiple-definition) resolves ts_malloc to mimalloc but ts_free can resolve to the CRT free (or vice-versa), corrupting the heap freelist and crashing mid-parse on large templated C++ headers (scales with parse churn, not syntax). cbm_init now calls ts_set_allocator(mi_malloc, mi_calloc, mi_realloc, mi_free) so ts allocate+free go through one allocator on every platform. Guarded to CFLAGS_PROD (MI_OVERRIDE=1); the test build stays CRT+ASan to avoid an alloc/free mismatch there. Tests in test_stack_overflow.c: allocator-binding mechanism + large-templated-C++ extraction guard (the C++ gap noted in #424).
PATH_MAX is defined by limits.h on the macOS-15 SDK (1024) and MSYS2 clang64 (260), so 'enum { PATH_MAX = 12 }' expanded to 'enum { 1024 = 12 }' → 'expected identifier' build failure on those platforms (Linux/macOS-14 didn't pull the macro into this TU). Renamed to K8S_PATH_DEPTH.
The namespace map kept only the first file per package (by platform-dependent iteration order). A same-package wildcard import (import com.example.* from a file itself in com.example) could resolve to the importer → self-edge suppressed → 0 IMPORTS on amd64 but 1 on arm64 (ei_kotlin_wildcard_import). Now store ALL files per namespace as a delimited list and resolve to the first non-importer sibling — deterministic on amd64/arm64. Re-key via the stored key pointer to avoid a use-after-free.
Remove leftover getenv(CBM_DBG_*) debug fprintf blocks in extract_calls.c/pass_calls.c (cppcheck always-true). Convert parse_lisp_imports to a stack-based walk (no recursion → no NOLINT, avoids deep-nesting overflow). Apply Homebrew clang-format to all touched sources. Local suite 5562/0, lint-ci clean.
fwcd/tree-sitter-kotlin wraps a whole class in an ERROR node when a member uses companion object / inner class combined with a delegation specifier. recover_kotlin_error_classes scans an ERROR node's flat tokens for class/object keyword + identifier, emits the Class def (respecting enclosing class QN) and collects bases from delegation_specifier siblings. Strictly additive (only fires on ERROR regions). Fixes inherit_kotlin (MyClass companion + Node inner).
pass_semantic + pass_parallel: when a decorator name resolves to no local def, upsert a synthetic '<decorator:NAME>' node (label Decorator) and emit DECORATES+USAGE — fixes rust #[derive]/#[allow], swift @discardableResult, scala @deprecated (deduped per project; pipeline-level so extraction histograms unaffected). py_lsp: inject a synthetic CBMCall for resolved subscript/operator dunders on typed receivers so they reach the CALLS pipeline — fixes python s[0]->Seq.__getitem__.
The unannotated param could not be resolved soundly (would require an unsound sole-class guess). Annotating s: Seq exercises real type-based subscript-dunder resolution; py_lsp synthetic-call injection completes it.
The legacy "Deploy from a branch" Pages build ran Jekyll on every push to
main and failed on the planning docs under /docs — Jekyll's Liquid parser
hit `Unknown tag 'data'` in EVALUATION_PLAN.md, so the website build went
red after nearly every commit.
/docs is a hand-written static site (index.html + robots/sitemap/llms.txt),
so it never needed Jekyll. Deploy it with a GitHub Actions workflow that
uploads /docs as-is and only triggers on docs/** changes, and add a
.nojekyll guard. Switch the Pages source to "GitHub Actions" for this to
take over from the legacy build.
Co-authored-by: DeusData <joerge@datadice.io>
Imports: per-grammar parsers for hare/pascal/powershell/scheme/racket/starlark/tcl/teal/zsh + css/html/cmake/bitbake/kconfig/gn/just/nix/jsonnet/pkl/nickel/thrift/capnp/d/tablegen/crystal/fsharp/ada; node-extraction for D/PowerShell/Pascal/Julia/F#; INHERITS for those. Pipeline edges: register Variable/Field defs for WRITES/READS resolution; CONFIGURES pass consuming env_accesses; DEPENDS_ON from go.mod/requirements.txt; SQS dot-form QN. Golden updates: julia +Class, just +Function (correct new extraction).
Final 4 probe suites (grammar_probe_d/e/f/g, 250 cases) bring node/edge-creation coverage to all 159 grammars. Triage fixed 4 fixture mislabels (objc Method label, smithy $version order, json non-ignored filename, sql DDL-only Variables) and confirmed ~46 real reds. Findings map to known classes 2/3 (import/include-edge resolution missing for ~34 grammars: agda/bicep/elm/func/move/nasm/pony/purescript/qml/smali/tlaplus/vhdl/protobuf/thrift/capnp/wit/smithy/astro/nix/nickel/pkl/jsonnet/hyprlang/devicetree/cmake/makefile/meson/gn/just/bitbake/kconfig/html/css/scss), 16 (node-extraction incompleteness: wolfram multi-def/set_top, wit world funcs, bicep resource, just recipe), plus infra gaps (INFRA_MAPS hcl/k8s only from YAML topic bindings; DEPENDS_ON only from Helm Chart.yaml — go.mod/requirements produce none). NEW class 17 = file-index routing gap: .env/.gitignore/.gitattributes/.re have no EXT_TABLE/FILENAME_TABLE entry in discover/language.c so index_repository never indexes them (direct extraction works). Vue/Svelte/Astro component imports: extraction works but JS/TS path resolver doesn't handle those extensions. All 159 grammars now have reproduce-first guards.
Extends the node/edge-creation hunt to ~35 grammar-only code languages (129 graph/extraction cases, 73 green guards / 56 real RED reproductions; triage confirmed 0 fixture errors — all reds are genuine product gaps). Findings map to: class 2 import-EXTRACTION missing (cbm_extract_imports dispatch in extract_imports.c covers only ~38 langs + default:break, so ~25 grammar-only langs' configured *_import_types are never consumed → 0 imports); class 1 INHERITS missing (crystal/julia/pascal/powershell/squirrel/solidity); and NEW class 16 = per-grammar NODE-extraction incompleteness (class_types configured + grammar node present, but extract_defs.c emits 0 type nodes for D struct/class, F# record/union, Gleam type, Julia/Odin struct, Pascal record, PowerShell class, Hare struct, ReScript type, squirrel class, sway struct/abi, tcl namespace, wgsl struct). Affected-grammar matrix in the suite comments. Brings code-grammar coverage to ~61 of 66.
Final hunt batches (convergence_probe 47, matrix_known_classes 44, matrix_new_constructs 63 = 154 graph-level cases) plus triage. Result: ZERO genuinely-new root-cause classes — every confirmed red maps to the already-known taxonomy (classes 1,4,5,6,7,12,13,14). Triage greened 5 fixture mis-builds: recursion self-calls (Go/Python/Rust/Java) produce 0 CALLS because self-loop edges are SUPPRESSED BY DESIGN (pass_calls.c/pass_parallel.c src->id==dst->id) — not a bug, fixtures now add a non-self caller; Ruby bare 'describe' parses as identifier not call (fixed to describe()). 26 reals kept as reproductions with // REAL BUG: root-cause comments. Confirms the bug hunt has converged: aggressive probing across new constructs + more languages surfaces only instances of the ~15 mapped classes.
Reproduce-first probes (215 graph-level cases) hunting node/edge-creation + LSP-pass bugs across 26 languages + web frameworks. node_creation_probe: 80/81 green (node creation is solid; only Go method-on-struct under threshold). lsp_resolution_probe: 72/83 green — RED reproductions for the cross-LSP dispatch gap (Rust/Kotlin S1 cross-file call) and constructor/static/virtual/generic resolution edge cases (C/C++/TS/Java/C#/PHP S3-S7). edge_types_probe: 29/51 green — candidate RED reproductions for HANDLES (7 web frameworks), HTTP_CALLS (5 clients), WRITES (all 5 langs), Kotlin throws/raises, Go DEFINES_METHOD/OVERRIDE (these need real-bug-vs-fixture verification). Green cases retained as regression guards throughout. No unrelated suite regressed.
Reproduce-first coverage of the pipeline step that turns extracted symbols/imports into resolved graph edges, across the 9 hybrid-LSP languages (91 graph-level cases). Live RED reproductions: IMPORTS edges are never created for Rust/Kotlin/Java/C#/PHP (all forms) despite working extraction (+ Python aliased/wildcard, TS re-export edge cases); cross-file CALLS do not resolve for Rust and Kotlin (lsp_cross pass processes 0 files for them) — missed by real-repo checks because same-file calls dominate; same-file IMPLEMENTS edges absent for Java/C# and Go implicit-interface satisfaction; same-file DECORATES absent for TS and C# attributes; cross-file USAGE absent for TS; cross-file INHERITS absent for TS/PHP/Kotlin/Python (extraction-propagated). Green guards retained for every working path (Go/Python/TS imports, cross-file CALLS for 7 langs, INHERITS for Java/C#/C++, etc.). 51 RED reproductions; no other suite affected.
Reproduce-first coverage of class-inheritance (base_classes) and import extraction across the 9 hybrid-LSP languages (~300 table-driven cases). Live RED reproductions: TS/TSX store the extends/implements keyword instead of the base type; PHP never populates base_classes and its use-imports parse the wrong node type; Kotlin does not parse the : supertype list; Python loses Generic[T] (paren/bracket leak); C++ drops qualified bases (std::vector) and leaks ':'; Rust misses several impl-Trait-for-T forms; C# import aliases (using F = X) and Python wildcard/__future__ imports are missed. C# inheritance and Go/TS/JS/Java/Kotlin/Rust import extraction pass as regression guards. Suite is RED until the extractors are fixed; 12 failing reproductions, no other suite affected.
Phase 16's FIFO writer-close surfaced as POLLHUP rather than a clean POLLIN+EOF, which the server's poll()-based read loop does not treat as shutdown, so the probe left the process alive and the smoke job failed on every platform. Switch to a plain '< /dev/null' immediate EOF — the server exits reliably (~1s) via its normal getline-EOF path, and it is fully portable (POSIX + MSYS2 bash), so no OS gate is needed. Verified end-to-end: bash scripts/smoke-test.sh reaches 'ALL PASSED'.
cbm_pkgmap_scan_repo was a no-op on Windows and pkgmap_walk_dir was POSIX-only (raw opendir/readdir), so package.json manifests were never read on Windows -> the pkgmap was empty -> bare @org/pkg workspace imports never resolved (cross-package IMPORTS = 0). The walk was disabled on Windows because a naive recursive descent hung on directory-junction cycles. Port it to the portable cbm_opendir/cbm_readdir API (as discover.c), guarantee termination with a hard PKGMAP_WALK_MAX_DEPTH=64 recursion bound (cannot hang regardless of junctions/symlink cycles), skip Windows reparse points and POSIX symlinks before descending, and remove the Windows gates. Verified: full macOS suite 4705 passed / 0 failed (contract_edge_workspaces_imports_issue408 now resolves on the POSIX path; Windows verified by CI).
log.c: check the buffer bound before reading raw[i] (arrayIndexThenCheck). mcp.c: compare excluded_count to INDEX_EXCLUDED_DIR_CAP directly for the 'truncated' flag (equivalent to >shown, but avoids cppcheck's knownConditionTrueFalse).
Phase 16 of smoke-test.sh launches the stdio MCP server with its stdin held open (a running instance), closes stdin to trigger shutdown, and asserts the process terminates within ~6s — guarding the orphaned-background-process failure mode behind #406 from the prod binary on every platform. POSIX-gated (Windows uses job objects; mkfifo is unavailable on MSYS2).
Two reviewed external-fork contributions, re-implemented as clean maintainer code.
#406 / PR #407 (thanks @nvt-pankajsharma): a POSIX parent-death watchdog so the stdio MCP server exits when its launching parent dies instead of lingering on stdin. Refactors signal_handler into an idempotent request_shutdown(); a watchdog thread polls getppid() (500ms) and shuts down + exit(0) once the initial ppid (>1) changes; Windows unaffected (#ifndef _WIN32). The fork's getppid()<=1 startup early-exit was dropped (it could wrongly kill a legitimately-launched server during reparent races / in container launchers; the initial_ppid>1 guard already no-ops safely), and thread-create failure is now non-fatal (matches the watcher/HTTP background-thread policy). Adds tests/test_parent_watchdog.sh + scripts/test.sh wiring.
#413 / PR #414 (thanks @santanusinha): runtime log-level control via CBM_LOG_LEVEL (case-insensitive debug|info|warn|error|none, or numeric 0..4; unset/unknown leaves the level unchanged). getenv() before threads start (no race). Adds tests/test_log.c cases + README env-var row.
A Lerna/Yarn workspaces monorepo where packages/b imports a sibling by package name (@org/a) should resolve via package.json name->dir into a cross-package IMPORTS edge. Reproduction contract; verifies whether the generic workspaces case resolves (cbm_pipeline_resolve_module already looks up bare specifiers in the pkgmap) or exposes a gap.