# Makefile.cbm — Build system for pure C rewrite # # Usage: # make -f Makefile.cbm test # Build + run all tests (ASan + UBSan) # make -f Makefile.cbm test-foundation # Foundation tests only (fast) # make -f Makefile.cbm test-tsan # Thread sanitizer build # make -f Makefile.cbm cbm # Production binary # make -f Makefile.cbm clean-c # Remove build artifacts # Compiler selection — override via: make CC=gcc CXX=g++ # macOS: cc (Apple Clang) — universal binary with ASan support # Linux: gcc/g++ — system default with full sanitizer support # CI scripts pass CC/CXX explicitly; don't rely on defaults here # Target architecture (macOS): build.sh/test.sh export ARCHFLAGS="-arch " # (see scripts/env.sh). Fold it into the compiler drivers with `override` so it # reaches EVERY compile and link recipe — including the vendored objects below # that use their own *_CFLAGS — and so it survives a command-line `CC=` override. # ARCHFLAGS is empty on Linux/Windows and for native direct `make` invocations, # leaving CC/CXX unchanged there. override CC := $(CC) $(ARCHFLAGS) override CXX := $(CXX) $(ARCHFLAGS) # ── Common flags ───────────────────────────────────────────────── # Include paths for: # src/ — new foundation headers # vendored/ — yyjson, xxhash, sqlite3 # internal/cbm — existing extraction headers (cbm.h, helpers.h, etc.) # internal/cbm/vendored/ts_runtime/include — tree-sitter API CBM_DIR = internal/cbm TS_INCLUDE = $(CBM_DIR)/vendored/ts_runtime/include # Vendored tree-sitter src/ contains unicode/ headers (umachine.h, utf.h, utf8.h). # This ensures we use our vendored copies instead of requiring system libicu-dev. TS_SRC = $(CBM_DIR)/vendored/ts_runtime/src # GCC-only warning suppressions (Clang rejects unknown -Wno-* with -Werror). # Detect GCC by checking for __GNUC__ without __clang__ — handles all versions. IS_GCC := $(shell echo | $(CC) -dM -E - 2>/dev/null | grep -q '__GNUC__' && ! echo | $(CC) -dM -E - 2>/dev/null | grep -q '__clang__' && echo yes || echo no) GCC_ONLY_FLAGS := ifeq ($(IS_GCC),yes) GCC_ONLY_FLAGS := -Wno-format-truncation -Wno-unused-result \ -Wno-stringop-truncation -Wno-alloc-size-larger-than endif CFLAGS_COMMON = -std=c11 -D_DEFAULT_SOURCE -D_GNU_SOURCE -Wall -Wextra -Werror \ -Wno-unused-parameter -Wno-sign-compare \ $(GCC_ONLY_FLAGS) \ -Isrc -Ivendored -Ivendored/sqlite3 \ -Ivendored/mimalloc/include \ -I$(CBM_DIR) -I$(TS_INCLUDE) CXXFLAGS_COMMON = -std=c++14 -Wall -Wextra -Werror \ -Wno-unused-parameter \ -I$(CBM_DIR) -I$(TS_INCLUDE) # Production flags (CFLAGS_EXTRA allows CI to inject -DCBM_VERSION) # CBM_BIND_TS_ALLOCATOR=1: bind the tree-sitter runtime to mimalloc (#424). Only # the prod build uses mimalloc (MI_OVERRIDE=1); the test build is CRT+ASan, where # binding would create an alloc/free mismatch, so the guard is prod-only. CFLAGS_PROD = $(CFLAGS_COMMON) -O2 -DCBM_BIND_TS_ALLOCATOR=1 $(CFLAGS_EXTRA) CXXFLAGS_PROD = $(CXXFLAGS_COMMON) -O2 # Test flags: debug + sanitizers (override SANITIZE= to disable on Windows) SANITIZE = -fsanitize=address,undefined -fno-omit-frame-pointer CFLAGS_TEST = $(CFLAGS_COMMON) -g -O1 $(SANITIZE) CXXFLAGS_TEST = $(CXXFLAGS_COMMON) -g -O1 $(SANITIZE) # TSan (can't combine with ASan) TSAN_SANITIZE = -fsanitize=thread -fno-omit-frame-pointer CFLAGS_TSAN = $(CFLAGS_COMMON) -g -O1 \ $(TSAN_SANITIZE) CXXFLAGS_TSAN = $(CXXFLAGS_COMMON) -g -O1 \ $(TSAN_SANITIZE) # Windows needs ws2_32 (Winsock), psapi (GetProcessMemoryInfo), shell32 # (CommandLineToArgvW — the UTF-8 argv path in main.c, #423/#20), and # --allow-multiple-definition (MinGW CRT symbol clashes). # Auto-detected via compiler; no manual override needed. IS_MINGW := $(shell echo | $(CC) -dM -E - 2>/dev/null | grep -q 'define _WIN32 ' && echo yes || echo no) WIN32_LIBS := ifeq ($(IS_MINGW),yes) WIN32_LIBS := -lws2_32 -lpsapi -lshell32 -Wl,--allow-multiple-definition -Wl,--stack,8388608 -static endif # STATIC=1 produces a fully static binary (for Alpine/musl portable builds) ifeq ($(STATIC),1) STATIC_FLAGS := -static endif LDFLAGS = -lm -lstdc++ -lpthread -lz $(WIN32_LIBS) $(STATIC_FLAGS) LDFLAGS_TEST = -lm -lstdc++ -lpthread -lz $(SANITIZE) $(WIN32_LIBS) LDFLAGS_TSAN = -lm -lstdc++ -lpthread -lz $(TSAN_SANITIZE) $(WIN32_LIBS) # ── Source files ───────────────────────────────────────────────── FOUNDATION_SRCS = \ src/foundation/arena.c \ src/foundation/hash_table.c \ src/foundation/str_intern.c \ src/foundation/log.c \ src/foundation/str_util.c \ src/foundation/platform.c \ src/foundation/system_info.c \ src/foundation/slab_alloc.c \ src/foundation/yaml.c \ src/foundation/compat.c \ src/foundation/compat_thread.c \ src/foundation/compat_fs.c \ src/foundation/compat_regex.c \ src/foundation/mem.c \ src/foundation/diagnostics.c \ src/foundation/profile.c \ src/foundation/dump_verify.c \ src/foundation/limits.c \ src/foundation/subprocess.c \ src/foundation/sha256.c # Existing extraction C code (compiled from current location) EXTRACTION_SRCS = \ $(CBM_DIR)/cbm.c \ $(CBM_DIR)/extract_defs.c \ $(CBM_DIR)/extract_calls.c \ $(CBM_DIR)/extract_imports.c \ $(CBM_DIR)/extract_usages.c \ $(CBM_DIR)/extract_unified.c \ $(CBM_DIR)/extract_semantic.c \ $(CBM_DIR)/extract_type_refs.c \ $(CBM_DIR)/extract_type_assigns.c \ $(CBM_DIR)/extract_env_accesses.c \ $(CBM_DIR)/extract_channels.c \ $(CBM_DIR)/extract_k8s.c \ $(CBM_DIR)/helpers.c \ $(CBM_DIR)/lang_specs.c \ $(CBM_DIR)/service_patterns.c # LSP resolvers (compiled as one unit via lsp_all.c) LSP_SRCS = $(CBM_DIR)/lsp_all.c # Header/source dependencies of the lsp_all unity object. lsp_all.c #includes # every lsp/*.c resolver, which in turn include cbm.h — and CBMCall is copied # BY VALUE across the lsp -> pipeline boundary (cbm_calls_push). This object is # compiled standalone and linked in, NOT recompiled with the rest on every link, # so it must list the headers/sources it pulls in. Without this, a changed struct # layout (e.g. a new CBMCall field) leaves a stale lsp_all.o with the old layout — # an ODR mismatch that under-reads the by-value struct copy. Explicit because this # Makefile does not use compiler auto-dependency (-MMD) generation. LSP_UNITY_DEPS = $(CBM_DIR)/lsp_all.c \ $(wildcard $(CBM_DIR)/lsp/*.c $(CBM_DIR)/lsp/*.h \ $(CBM_DIR)/lsp/generated/*.c $(CBM_DIR)/*.h) # Tree-sitter runtime TS_RUNTIME_SRC = $(CBM_DIR)/ts_runtime.c # All 64 grammar shim files GRAMMAR_SRCS = $(wildcard $(CBM_DIR)/grammar_*.c) # LZ4 + Aho-Corasick AC_LZ4_SRCS = $(CBM_DIR)/ac.c $(CBM_DIR)/lz4_store.c # Zstd compression (for persistent artifacts) ZSTD_SRCS = $(CBM_DIR)/zstd_store.c # Preprocessor (C++) PREPROCESSOR_SRC = $(CBM_DIR)/preprocessor.cpp # SQLite writer SQLITE_WRITER_SRC = $(CBM_DIR)/sqlite_writer.c # Store module (new) STORE_SRCS = src/store/store.c # Cypher module (new) CYPHER_SRCS = src/cypher/cypher.c # MCP server module (new) MCP_SRCS = src/mcp/mcp.c src/mcp/index_supervisor.c src/mcp/compact_out.c # Discover module (new) DISCOVER_SRCS = \ src/discover/language.c \ src/discover/userconfig.c \ src/discover/gitignore.c \ src/discover/discover.c # Graph buffer module (new) GRAPH_BUFFER_SRCS = src/graph_buffer/graph_buffer.c # Pipeline module (new) PIPELINE_SRCS = \ src/pipeline/fqn.c \ src/pipeline/path_alias.c \ src/pipeline/registry.c \ src/pipeline/pipeline.c \ src/pipeline/pipeline_incremental.c \ src/pipeline/worker_pool.c \ src/pipeline/pass_parallel.c \ src/pipeline/pass_definitions.c \ src/pipeline/pass_calls.c \ src/pipeline/pass_lsp_cross.c \ src/pipeline/pass_usages.c \ src/pipeline/pass_semantic.c \ src/pipeline/pass_tests.c \ src/pipeline/pass_githistory.c \ src/pipeline/pass_gitdiff.c \ src/pipeline/pass_configures.c \ src/pipeline/pass_configlink.c \ src/pipeline/pass_route_nodes.c \ src/pipeline/pass_enrichment.c \ src/pipeline/pass_envscan.c \ src/pipeline/pass_compile_commands.c \ src/pipeline/pass_infrascan.c \ src/pipeline/pass_k8s.c \ src/pipeline/pass_similarity.c \ src/pipeline/pass_semantic_edges.c \ src/pipeline/pass_complexity.c \ src/pipeline/pass_cross_repo.c \ src/pipeline/artifact.c \ src/pipeline/pass_pkgmap.c # SimHash / MinHash module SIMHASH_SRCS = src/simhash/minhash.c # Semantic embedding module SEMANTIC_SRCS = src/semantic/semantic.c src/semantic/ast_profile.c src/semantic/rotsq.c # nomic-embed-code pretrained vectors (assembler blob) UNIXCODER_BLOB_SRC = vendored/nomic/code_vectors_blob.S # Traces module (new) TRACES_SRCS = src/traces/traces.c # Watcher module (new) WATCHER_SRCS = src/watcher/watcher.c # Git context module (new) GIT_SRCS = src/git/git_context.c # CLI module (new) CLI_SRCS = src/cli/cli.c src/cli/progress_sink.c src/cli/hook_augment.c # UI module (graph visualization) UI_SRCS = \ src/ui/config.c \ src/ui/http_server.c \ src/ui/layout3d.c \ src/ui/httpd.c \ src/ui/embedded_stub.c # mimalloc (vendored, global allocator override) # # Override strategy is platform-specific: # * Unix (macOS/Linux): rely on static-link-order override — the prod mimalloc # object is linked first so its strong malloc/free symbols win. We do NOT # define MI_MALLOC_OVERRIDE here: that would compile alloc-override.c's # forwarding definitions (malloc/free/posix_memalign/...) which, on macOS's # two-level namespace, makes the binary's free == mi_free while system # libraries keep allocating via the system allocator — pointers crossing that # boundary hit "mimalloc: error: mi_free: invalid pointer". (Repro: # `codebase-memory-mcp index ` aborted on every run.) # * Windows (MinGW, static CRT): the link-order trick fails (#424's allocator # mismatch), so we define MI_MALLOC_OVERRIDE=1 to compile the static-CRT # override added in mimalloc 3.3.0 (the _MSC_VER / _ACRTIMP / # _CRT_HYBRIDPATCHABLE entry points) which take precedence over the static # CRT's malloc/free. # Note: mimalloc's source gates the override body on MI_MALLOC_OVERRIDE (CMake # derives it from the MI_OVERRIDE option); MI_OVERRIDE alone is not read by the # source — it is kept here only as this project's prod/test marker. MIMALLOC_SRC = vendored/mimalloc/src/static.c MIMALLOC_OVERRIDE_DEFINE := ifeq ($(IS_MINGW),yes) MIMALLOC_OVERRIDE_DEFINE := -DMI_MALLOC_OVERRIDE=1 endif MIMALLOC_CFLAGS = -std=c11 -O2 -w \ -Ivendored/mimalloc/include \ -Ivendored/mimalloc/src \ -DMI_OVERRIDE=1 \ $(MIMALLOC_OVERRIDE_DEFINE) MIMALLOC_CFLAGS_TEST = -std=c11 -g -O1 -w \ -Ivendored/mimalloc/include \ -Ivendored/mimalloc/src \ -DMI_OVERRIDE=0 # sqlite3 (vendored amalgamation — compiled ourselves for ASan instrumentation) # SQLITE_ENABLE_FTS5: enables the FTS5 full-text search extension used by the # BM25 search path in search_graph (see nodes_fts virtual table in store.c). SQLITE3_SRC = vendored/sqlite3/sqlite3.c SQLITE3_CFLAGS = -std=c11 -O2 -w -DSQLITE_DQS=0 -DSQLITE_THREADSAFE=1 -DSQLITE_ENABLE_FTS5 SQLITE3_CFLAGS_TEST = -std=c11 -g -O1 -w -DSQLITE_DQS=0 -DSQLITE_THREADSAFE=1 -DSQLITE_ENABLE_FTS5 # TRE regex (vendored, Windows only — POSIX uses system ) TRE_SRC = vendored/tre/tre_all.c TRE_CFLAGS = -std=c11 -g -O1 -w -Ivendored/tre # yyjson (vendored) YYJSON_SRC = vendored/yyjson/yyjson.c # All production sources PROD_SRCS = $(FOUNDATION_SRCS) $(STORE_SRCS) $(CYPHER_SRCS) $(MCP_SRCS) $(DISCOVER_SRCS) $(GRAPH_BUFFER_SRCS) $(PIPELINE_SRCS) $(SIMHASH_SRCS) $(SEMANTIC_SRCS) $(TRACES_SRCS) $(WATCHER_SRCS) $(GIT_SRCS) $(CLI_SRCS) $(UI_SRCS) $(YYJSON_SRC) EXISTING_C_SRCS = $(EXTRACTION_SRCS) $(LSP_SRCS) $(TS_RUNTIME_SRC) \ $(GRAMMAR_SRCS) $(AC_LZ4_SRCS) $(ZSTD_SRCS) $(SQLITE_WRITER_SRC) # ── Test sources ───────────────────────────────────────────────── TEST_FOUNDATION_SRCS = \ tests/test_main.c \ tests/test_arena.c \ tests/test_hash_table.c \ tests/test_dyn_array.c \ tests/test_str_intern.c \ tests/test_log.c \ tests/test_str_util.c \ tests/test_platform.c \ tests/test_dump_verify.c \ tests/test_subprocess.c TEST_EXTRACTION_SRCS = \ tests/test_extraction.c \ tests/test_extraction_inheritance.c \ tests/test_extraction_imports.c \ tests/test_parse_coverage.c \ tests/test_grammar_regression.c \ tests/test_grammar_labels.c \ tests/test_grammar_imports.c \ tests/test_ac.c TEST_STORE_SRCS = \ tests/test_store_nodes.c \ tests/test_store_edges.c \ tests/test_store_search.c \ tests/test_store_arch.c \ tests/test_store_bulk.c \ tests/test_store_pragmas.c \ tests/test_store_checkpoint.c \ tests/test_dump_verify_io.c TEST_CYPHER_SRCS = \ tests/test_cypher.c TEST_MCP_SRCS = \ tests/test_mcp.c TEST_DISCOVER_SRCS = \ tests/test_language.c \ tests/test_userconfig.c \ tests/test_gitignore.c \ tests/test_git_context.c \ tests/test_discover.c TEST_GRAPH_BUFFER_SRCS = tests/test_graph_buffer.c TEST_PIPELINE_SRCS = tests/test_registry.c tests/test_pipeline.c tests/test_fqn.c tests/test_route_canon.c tests/test_path_alias.c tests/test_configlink.c tests/test_infrascan.c tests/test_worker_pool.c tests/test_parallel.c tests/test_index_resilience.c TEST_WATCHER_SRCS = tests/test_watcher.c TEST_LZ4_SRCS = tests/test_lz4.c TEST_ZSTD_SRCS = tests/test_zstd.c TEST_ARTIFACT_SRCS = tests/test_artifact.c TEST_SQLITE_WRITER_SRCS = tests/test_sqlite_writer.c TEST_GO_LSP_SRCS = tests/test_go_lsp.c TEST_C_LSP_SRCS = tests/test_c_lsp.c TEST_PHP_LSP_SRCS = tests/test_php_lsp.c 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 TEST_PY_LSP_SRCS = tests/test_py_lsp.c TEST_PY_LSP_BENCH_SRCS = tests/test_py_lsp_bench.c TEST_PY_LSP_STRESS_SRCS = tests/test_py_lsp_stress.c TEST_PY_LSP_SCALE_SRCS = tests/test_py_lsp_scale.c TEST_TS_LSP_SRCS = tests/test_ts_lsp.c TEST_JAVA_LSP_SRCS = tests/test_java_lsp.c tests/test_java_lsp_coverage.c TEST_KOTLIN_LSP_SRCS = tests/test_kotlin_lsp.c TEST_RUST_LSP_SRCS = tests/test_rust_lsp.c TEST_INTEGRATION_SRCS = tests/test_integration.c tests/test_incremental.c tests/test_lang_contract.c tests/test_edge_imports.c tests/test_edge_structural.c tests/test_lsp_resolution_probe.c tests/test_node_creation_probe.c tests/test_edge_types_probe.c tests/test_convergence_probe.c tests/test_matrix_known_classes.c tests/test_matrix_new_constructs.c tests/test_grammar_probe_a.c tests/test_grammar_probe_b.c tests/test_grammar_probe_c.c tests/test_grammar_probe_d.c tests/test_grammar_probe_e.c tests/test_grammar_probe_f.c tests/test_grammar_probe_g.c TEST_TRACES_SRCS = tests/test_traces.c TEST_CLI_SRCS = tests/test_cli.c TEST_MEM_SRCS = tests/test_mem.c TEST_UI_SRCS = tests/test_ui.c TEST_HTTPD_SRCS = tests/test_httpd.c TEST_SECURITY_SRCS = tests/test_security.c TEST_YAML_SRCS = tests/test_yaml.c TEST_SEMANTIC_SRCS = tests/test_semantic.c TEST_AST_PROFILE_SRCS = tests/test_ast_profile.c TEST_SLAB_ALLOC_SRCS = tests/test_slab_alloc.c TEST_SIMHASH_SRCS = tests/test_simhash.c TEST_STACK_OVERFLOW_SRCS = tests/test_stack_overflow.c # Cumulative BUG-REPRODUCTION suite (separate runner, NOT in ALL_TEST_SRCS). # These cases are RED by design (one open bug each) — see tests/repro/repro_main.c. # Kept out of the gating `make test` so `ci-ok` stays green; run via `make test-repro`. TEST_REPRO_SRCS = \ tests/repro/repro_main.c \ tests/repro/repro_parallel_determinism.c \ tests/repro/repro_extraction.c \ tests/repro/repro_issue495.c \ tests/repro/repro_issue521.c \ tests/repro/repro_issue382.c \ tests/repro/repro_issue408.c \ tests/repro/repro_issue56.c \ tests/repro/repro_issue480.c \ tests/repro/repro_issue571.c \ tests/repro/repro_issue523.c \ tests/repro/repro_issue546.c \ tests/repro/repro_issue627.c \ tests/repro/repro_issue514.c \ tests/repro/repro_issue510.c \ tests/repro/repro_issue557.c \ tests/repro/repro_issue520.c \ tests/repro/repro_issue333.c \ tests/repro/repro_issue570.c \ tests/repro/repro_issue409.c \ tests/repro/repro_issue431.c \ tests/repro/repro_issue607.c \ tests/repro/repro_issue403.c \ tests/repro/repro_issue434.c \ tests/repro/repro_issue471.c \ tests/repro/repro_issue221.c \ tests/repro/repro_issue548.c \ tests/repro/repro_new_ts_class_field_arrow.c \ tests/repro/repro_new_py_tuple_unpack.c \ tests/repro/repro_new_cypher_limit_zero.c \ tests/repro/repro_issue363.c \ tests/repro/repro_issue581.c \ tests/repro/repro_issue787.c \ tests/repro/repro_issue842.c \ tests/repro/repro_issue964.c \ tests/repro/repro_invariant_calls.c \ tests/repro/repro_invariant_graph.c \ tests/repro/repro_invariant_breadth.c \ tests/repro/repro_invariant_enclosing_parity.c \ tests/repro/repro_invariant_lsp_rescue.c \ tests/repro/repro_invariant_discovery_fqn.c \ tests/repro/repro_grammar_core.c \ tests/repro/repro_grammar_scripting.c \ tests/repro/repro_grammar_functional.c \ tests/repro/repro_grammar_systems.c \ tests/repro/repro_grammar_web.c \ tests/repro/repro_grammar_config.c \ tests/repro/repro_grammar_build.c \ tests/repro/repro_grammar_shells.c \ tests/repro/repro_grammar_scientific.c \ tests/repro/repro_grammar_markup.c \ tests/repro/repro_grammar_misc.c \ tests/repro/repro_lsp_c_cpp.c \ tests/repro/repro_lsp_go_py.c \ tests/repro/repro_lsp_ts.c \ tests/repro/repro_ts_inherited_method.c \ 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_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 ──────────────────────────────────────────── BUILD_DIR = build/c # ── Object file compilation (grammars need relaxed warnings) ───── # Grammar + tree-sitter runtime: compiled without -Werror (upstream code has warnings) GRAMMAR_CFLAGS = -std=c11 -D_DEFAULT_SOURCE -O2 -w -I$(CBM_DIR) -I$(TS_INCLUDE) -I$(TS_SRC) GRAMMAR_CFLAGS_TEST = -std=c11 -D_DEFAULT_SOURCE -g -O1 -w -I$(CBM_DIR) -I$(TS_INCLUDE) -I$(TS_SRC) \ $(SANITIZE) GRAMMAR_CFLAGS_TSAN = -std=c11 -D_DEFAULT_SOURCE -g -O1 -w -I$(CBM_DIR) -I$(TS_INCLUDE) -I$(TS_SRC) \ $(TSAN_SANITIZE) # Object files for grammars + ts_runtime + lsp_all + preprocessor GRAMMAR_OBJS_TEST = $(patsubst $(CBM_DIR)/%.c,$(BUILD_DIR)/%.o,$(GRAMMAR_SRCS)) TS_RUNTIME_OBJ_TEST = $(BUILD_DIR)/ts_runtime.o LSP_OBJ_TEST = $(BUILD_DIR)/lsp_all.o PP_OBJ_TEST = $(BUILD_DIR)/preprocessor.o GRAMMAR_OBJS_TSAN = $(patsubst $(CBM_DIR)/%.c,$(BUILD_DIR)/tsan_%.o,$(GRAMMAR_SRCS)) TS_RUNTIME_OBJ_TSAN = $(BUILD_DIR)/tsan_ts_runtime.o LSP_OBJ_TSAN = $(BUILD_DIR)/tsan_lsp_all.o PP_OBJ_TSAN = $(BUILD_DIR)/tsan_preprocessor.o # ── Targets ────────────────────────────────────────────────────── .PHONY: test test-repro test-foundation test-tsan cbm cbm-with-ui frontend embed clean-c lint lint-tidy lint-cppcheck lint-format security $(BUILD_DIR): mkdir -p $(BUILD_DIR) # ── Foundation-only test (fast, no extraction) ─────────────────── $(BUILD_DIR)/test-foundation: $(TEST_FOUNDATION_SRCS) $(FOUNDATION_SRCS) | $(BUILD_DIR) $(CC) $(CFLAGS_TEST) -o $@ $(TEST_FOUNDATION_SRCS) $(FOUNDATION_SRCS) $(LDFLAGS_TEST) test-foundation: $(BUILD_DIR)/test-foundation cd $(CURDIR) && $(BUILD_DIR)/test-foundation # ── Grammar/TS/LSP object files (compiled with relaxed warnings) ─ $(BUILD_DIR)/%.o: $(CBM_DIR)/%.c | $(BUILD_DIR) $(CC) $(GRAMMAR_CFLAGS_TEST) -c -o $@ $< $(BUILD_DIR)/ts_runtime.o: $(CBM_DIR)/ts_runtime.c | $(BUILD_DIR) $(CC) $(GRAMMAR_CFLAGS_TEST) -c -o $@ $< $(BUILD_DIR)/lsp_all.o: $(LSP_UNITY_DEPS) | $(BUILD_DIR) $(CC) $(GRAMMAR_CFLAGS_TEST) $(SANITIZE) -c -o $@ $< $(BUILD_DIR)/preprocessor.o: $(CBM_DIR)/preprocessor.cpp | $(BUILD_DIR) $(CXX) $(CXXFLAGS_TEST) -w -I$(CBM_DIR)/vendored -c -o $@ $< $(BUILD_DIR)/tsan_%.o: $(CBM_DIR)/%.c | $(BUILD_DIR) $(CC) $(GRAMMAR_CFLAGS_TSAN) -c -o $@ $< $(BUILD_DIR)/tsan_ts_runtime.o: $(CBM_DIR)/ts_runtime.c | $(BUILD_DIR) $(CC) $(GRAMMAR_CFLAGS_TSAN) -c -o $@ $< $(BUILD_DIR)/tsan_lsp_all.o: $(LSP_UNITY_DEPS) | $(BUILD_DIR) $(CC) $(GRAMMAR_CFLAGS_TSAN) -c -o $@ $< $(BUILD_DIR)/tsan_preprocessor.o: $(CBM_DIR)/preprocessor.cpp | $(BUILD_DIR) $(CXX) $(CXXFLAGS_TSAN) -w -I$(CBM_DIR)/vendored -c -o $@ $< # ── Full test binary ───────────────────────────────────────────── # mimalloc object files MIMALLOC_OBJ_TEST = $(BUILD_DIR)/mimalloc.o MIMALLOC_OBJ_TSAN = $(BUILD_DIR)/tsan_mimalloc.o MIMALLOC_OBJ_PROD = $(BUILD_DIR)/prod_mimalloc.o $(BUILD_DIR)/mimalloc.o: $(MIMALLOC_SRC) | $(BUILD_DIR) $(CC) $(MIMALLOC_CFLAGS_TEST) -c -o $@ $< $(BUILD_DIR)/tsan_mimalloc.o: $(MIMALLOC_SRC) | $(BUILD_DIR) $(CC) $(MIMALLOC_CFLAGS_TEST) $(TSAN_SANITIZE) -c -o $@ $< $(BUILD_DIR)/prod_mimalloc.o: $(MIMALLOC_SRC) | $(BUILD_DIR) $(CC) $(MIMALLOC_CFLAGS) -c -o $@ $< # sqlite3 object files (vendored amalgamation) SQLITE3_OBJ_TEST = $(BUILD_DIR)/sqlite3.o SQLITE3_OBJ_TSAN = $(BUILD_DIR)/tsan_sqlite3.o SQLITE3_OBJ_PROD = $(BUILD_DIR)/prod_sqlite3.o $(BUILD_DIR)/sqlite3.o: $(SQLITE3_SRC) | $(BUILD_DIR) $(CC) $(SQLITE3_CFLAGS_TEST) $(SANITIZE) -c -o $@ $< $(BUILD_DIR)/tsan_sqlite3.o: $(SQLITE3_SRC) | $(BUILD_DIR) $(CC) $(SQLITE3_CFLAGS_TEST) $(TSAN_SANITIZE) -c -o $@ $< $(BUILD_DIR)/prod_sqlite3.o: $(SQLITE3_SRC) | $(BUILD_DIR) $(CC) $(SQLITE3_CFLAGS) -c -o $@ $< # TRE regex (only compiled on Windows — POSIX uses system ) TRE_OBJ_TEST := TRE_OBJ_TSAN := TRE_OBJ_PROD := ifeq ($(IS_MINGW),yes) TRE_OBJ_TEST = $(BUILD_DIR)/tre.o TRE_OBJ_TSAN = $(BUILD_DIR)/tsan_tre.o TRE_OBJ_PROD = $(BUILD_DIR)/prod_tre.o $(BUILD_DIR)/tre.o: $(TRE_SRC) | $(BUILD_DIR) $(CC) $(TRE_CFLAGS) $(SANITIZE) -fno-sanitize=alignment -c -o $@ $< $(BUILD_DIR)/tsan_tre.o: $(TRE_SRC) | $(BUILD_DIR) $(CC) $(TRE_CFLAGS) $(TSAN_SANITIZE) -c -o $@ $< $(BUILD_DIR)/prod_tre.o: $(TRE_SRC) | $(BUILD_DIR) $(CC) $(TRE_CFLAGS) -O2 -c -o $@ $< endif # Vendored LZ4 (test build) LZ4_OBJ_TEST = $(BUILD_DIR)/test_lz4.o $(BUILD_DIR)/test_lz4hc.o LZ4_OBJ_TSAN = $(BUILD_DIR)/tsan_lz4.o $(BUILD_DIR)/tsan_lz4hc.o $(BUILD_DIR)/test_lz4.o: $(CBM_DIR)/vendored/lz4/lz4.c | $(BUILD_DIR) $(CC) -std=c11 -D_DEFAULT_SOURCE -g -O1 $(SANITIZE) -w -I$(CBM_DIR) -c -o $@ $< $(BUILD_DIR)/test_lz4hc.o: $(CBM_DIR)/vendored/lz4/lz4hc.c | $(BUILD_DIR) $(CC) -std=c11 -D_DEFAULT_SOURCE -g -O1 $(SANITIZE) -w -I$(CBM_DIR)/vendored/lz4 -c -o $@ $< $(BUILD_DIR)/tsan_lz4.o: $(CBM_DIR)/vendored/lz4/lz4.c | $(BUILD_DIR) $(CC) -std=c11 -D_DEFAULT_SOURCE -g -O1 $(TSAN_SANITIZE) -w -I$(CBM_DIR) -c -o $@ $< $(BUILD_DIR)/tsan_lz4hc.o: $(CBM_DIR)/vendored/lz4/lz4hc.c | $(BUILD_DIR) $(CC) -std=c11 -D_DEFAULT_SOURCE -g -O1 $(TSAN_SANITIZE) -w -I$(CBM_DIR)/vendored/lz4 -c -o $@ $< # Vendored zstd (test build) ZSTD_OBJ_TEST = $(BUILD_DIR)/test_zstd.o ZSTD_OBJ_TSAN = $(BUILD_DIR)/tsan_zstd.o $(BUILD_DIR)/test_zstd.o: $(CBM_DIR)/vendored/zstd/zstd.c | $(BUILD_DIR) $(CC) -std=c11 -D_DEFAULT_SOURCE -g -O1 $(SANITIZE) -w -I$(CBM_DIR)/vendored/zstd -c -o $@ $< $(BUILD_DIR)/tsan_zstd.o: $(CBM_DIR)/vendored/zstd/zstd.c | $(BUILD_DIR) $(CC) -std=c11 -D_DEFAULT_SOURCE -g -O1 $(TSAN_SANITIZE) -w -I$(CBM_DIR)/vendored/zstd -c -o $@ $< # nomic-embed-code pretrained vector blob UNIXCODER_OBJ = $(BUILD_DIR)/unixcoder_blob.o $(UNIXCODER_OBJ): $(UNIXCODER_BLOB_SRC) vendored/nomic/code_vectors.bin | $(BUILD_DIR) $(CC) -c -o $@ $< OBJS_VENDORED_TEST = $(MIMALLOC_OBJ_TEST) $(SQLITE3_OBJ_TEST) $(TRE_OBJ_TEST) $(GRAMMAR_OBJS_TEST) $(TS_RUNTIME_OBJ_TEST) $(LSP_OBJ_TEST) $(PP_OBJ_TEST) $(LZ4_OBJ_TEST) $(ZSTD_OBJ_TEST) $(UNIXCODER_OBJ) OBJS_VENDORED_TSAN = $(MIMALLOC_OBJ_TSAN) $(SQLITE3_OBJ_TSAN) $(TRE_OBJ_TSAN) $(GRAMMAR_OBJS_TSAN) $(TS_RUNTIME_OBJ_TSAN) $(LSP_OBJ_TSAN) $(PP_OBJ_TSAN) $(LZ4_OBJ_TSAN) $(ZSTD_OBJ_TSAN) $(UNIXCODER_OBJ) $(BUILD_DIR)/test-runner: $(ALL_TEST_SRCS) $(PROD_SRCS) $(EXTRACTION_SRCS) $(AC_LZ4_SRCS) $(ZSTD_SRCS) $(SQLITE_WRITER_SRC) $(OBJS_VENDORED_TEST) | $(BUILD_DIR) $(CC) $(CFLAGS_TEST) -Itests -Itests/repro -o $@ \ $(ALL_TEST_SRCS) $(PROD_SRCS) \ $(EXTRACTION_SRCS) $(AC_LZ4_SRCS) $(ZSTD_SRCS) $(SQLITE_WRITER_SRC) \ $(OBJS_VENDORED_TEST) \ $(LDFLAGS_TEST) test: $(BUILD_DIR)/test-runner cd $(CURDIR) && $(BUILD_DIR)/test-runner # ── Cumulative bug-reproduction runner (RED by design, non-gating) ── # Mirrors test-runner's link line but uses repro_main.c (own main + counters) # and TEST_REPRO_SRCS instead of ALL_TEST_SRCS. Exits non-zero while any bug is # still reproduced (the expected state); bug-repro.yml surfaces it as a board. $(BUILD_DIR)/test-repro-runner: $(TEST_REPRO_SRCS) $(PROD_SRCS) $(EXTRACTION_SRCS) $(AC_LZ4_SRCS) $(ZSTD_SRCS) $(SQLITE_WRITER_SRC) $(OBJS_VENDORED_TEST) | $(BUILD_DIR) $(CC) $(CFLAGS_TEST) -Itests -o $@ \ $(TEST_REPRO_SRCS) $(PROD_SRCS) \ $(EXTRACTION_SRCS) $(AC_LZ4_SRCS) $(ZSTD_SRCS) $(SQLITE_WRITER_SRC) \ $(OBJS_VENDORED_TEST) \ $(LDFLAGS_TEST) test-repro: $(BUILD_DIR)/test-repro-runner cd $(CURDIR) && $(BUILD_DIR)/test-repro-runner # ── TSan full test ─────────────────────────────────────────────── TEST_TSAN_SUITES ?= mem slab_alloc parallel TSAN_OPTIONS ?= halt_on_error=1 $(BUILD_DIR)/test-runner-tsan: $(ALL_TEST_SRCS) $(PROD_SRCS) $(EXTRACTION_SRCS) $(AC_LZ4_SRCS) $(ZSTD_SRCS) $(SQLITE_WRITER_SRC) $(OBJS_VENDORED_TSAN) | $(BUILD_DIR) $(CC) $(CFLAGS_TSAN) -Itests -Itests/repro -o $@ \ $(ALL_TEST_SRCS) $(PROD_SRCS) \ $(EXTRACTION_SRCS) $(AC_LZ4_SRCS) $(ZSTD_SRCS) $(SQLITE_WRITER_SRC) \ $(OBJS_VENDORED_TSAN) \ $(LDFLAGS_TSAN) test-tsan: $(BUILD_DIR)/test-runner-tsan cd $(CURDIR) && TSAN_OPTIONS="$(TSAN_OPTIONS)" $(BUILD_DIR)/test-runner-tsan $(TEST_TSAN_SUITES) # ── Production binary ──────────────────────────────────────────── # Grammar/TS/LSP objects for production (compiled with relaxed warnings, -O2) GRAMMAR_OBJS_PROD = $(patsubst $(CBM_DIR)/%.c,$(BUILD_DIR)/prod_%.o,$(GRAMMAR_SRCS)) TS_RUNTIME_OBJ_PROD = $(BUILD_DIR)/prod_ts_runtime.o LSP_OBJ_PROD = $(BUILD_DIR)/prod_lsp_all.o PP_OBJ_PROD = $(BUILD_DIR)/prod_preprocessor.o $(BUILD_DIR)/prod_%.o: $(CBM_DIR)/%.c | $(BUILD_DIR) $(CC) $(GRAMMAR_CFLAGS) -c -o $@ $< $(BUILD_DIR)/prod_ts_runtime.o: $(CBM_DIR)/ts_runtime.c | $(BUILD_DIR) $(CC) $(GRAMMAR_CFLAGS) -c -o $@ $< $(BUILD_DIR)/prod_lsp_all.o: $(LSP_UNITY_DEPS) | $(BUILD_DIR) $(CC) $(GRAMMAR_CFLAGS) -c -o $@ $< $(BUILD_DIR)/prod_preprocessor.o: $(CBM_DIR)/preprocessor.cpp | $(BUILD_DIR) $(CXX) $(CXXFLAGS_PROD) -w -I$(CBM_DIR)/vendored -c -o $@ $< # Vendored LZ4 (compiled separately, not unity-built via lz4_store.c) LZ4_OBJ_PROD = $(BUILD_DIR)/prod_lz4.o $(BUILD_DIR)/prod_lz4hc.o $(BUILD_DIR)/prod_lz4.o: $(CBM_DIR)/vendored/lz4/lz4.c | $(BUILD_DIR) $(CC) -std=c11 -D_DEFAULT_SOURCE -O2 -w -I$(CBM_DIR) -c -o $@ $< $(BUILD_DIR)/prod_lz4hc.o: $(CBM_DIR)/vendored/lz4/lz4hc.c | $(BUILD_DIR) $(CC) -std=c11 -D_DEFAULT_SOURCE -O2 -w -I$(CBM_DIR)/vendored/lz4 -c -o $@ $< # Vendored zstd (compiled separately, not unity-built via zstd_store.c) ZSTD_OBJ_PROD = $(BUILD_DIR)/prod_zstd.o $(BUILD_DIR)/prod_zstd.o: $(CBM_DIR)/vendored/zstd/zstd.c | $(BUILD_DIR) $(CC) -std=c11 -D_DEFAULT_SOURCE -O2 -w -I$(CBM_DIR)/vendored/zstd -c -o $@ $< OBJS_VENDORED_PROD = $(MIMALLOC_OBJ_PROD) $(SQLITE3_OBJ_PROD) $(TRE_OBJ_PROD) $(GRAMMAR_OBJS_PROD) $(TS_RUNTIME_OBJ_PROD) $(LSP_OBJ_PROD) $(PP_OBJ_PROD) $(LZ4_OBJ_PROD) $(ZSTD_OBJ_PROD) $(UNIXCODER_OBJ) MAIN_SRC = src/main.c $(BUILD_DIR)/codebase-memory-mcp: $(MAIN_SRC) $(PROD_SRCS) $(EXTRACTION_SRCS) $(AC_LZ4_SRCS) $(ZSTD_SRCS) $(SQLITE_WRITER_SRC) $(OBJS_VENDORED_PROD) | $(BUILD_DIR) $(CC) $(CFLAGS_PROD) -o $@ \ $(MAIN_SRC) $(PROD_SRCS) \ $(EXTRACTION_SRCS) $(AC_LZ4_SRCS) $(ZSTD_SRCS) $(SQLITE_WRITER_SRC) \ $(OBJS_VENDORED_PROD) \ $(LDFLAGS) cbm: $(BUILD_DIR)/codebase-memory-mcp @echo "Built: $(BUILD_DIR)/codebase-memory-mcp" # ── Build with embedded UI (requires Node.js) ─────────────────── # Swap embedded_stub.c for the generated embedded_assets.c UI_SRCS_WITH_ASSETS = $(subst src/ui/embedded_stub.c,src/ui/embedded_assets.c,$(UI_SRCS)) PROD_SRCS_WITH_ASSETS = $(subst src/ui/embedded_stub.c,src/ui/embedded_assets.c,$(PROD_SRCS)) # Embedded asset object files (generated by embed script) EMBED_OBJS = $(wildcard $(BUILD_DIR)/embedded/embed_*.o) frontend: cd graph-ui && npm ci && npm run build embed: frontend scripts/embed-frontend.sh graph-ui/dist $(BUILD_DIR)/embedded cbm-with-ui: embed $(OBJS_VENDORED_PROD) $(CC) $(CFLAGS_PROD) -o $(BUILD_DIR)/codebase-memory-mcp \ $(MAIN_SRC) $(PROD_SRCS_WITH_ASSETS) \ $(EXTRACTION_SRCS) $(AC_LZ4_SRCS) $(ZSTD_SRCS) $(SQLITE_WRITER_SRC) \ $(OBJS_VENDORED_PROD) \ $(wildcard $(BUILD_DIR)/embedded/embed_*.o) \ $(LDFLAGS) @echo "Built with UI: $(BUILD_DIR)/codebase-memory-mcp" clean-c: rm -rf $(BUILD_DIR) # ── Linting ───────────────────────────────────────────────────── # clang-tidy and clang-format: use Homebrew LLVM on macOS, overridable via make args # e.g. make lint-format CLANG_FORMAT=clang-format-18 LLVM_BIN := $(shell brew --prefix llvm 2>/dev/null)/bin CLANG_TIDY ?= $(shell [ -x "$(LLVM_BIN)/clang-tidy" ] && echo "$(LLVM_BIN)/clang-tidy" || echo clang-tidy) CLANG_FORMAT ?= $(shell [ -x "$(LLVM_BIN)/clang-format" ] && echo "$(LLVM_BIN)/clang-format" || echo clang-format) CPPCHECK ?= cppcheck # macOS SDK sysroot (needed for Homebrew LLVM to find system headers) SYSROOT = $(shell xcrun --show-sdk-path 2>/dev/null) SYSROOT_FLAG = $(if $(SYSROOT),-isysroot $(SYSROOT),) # Our source files (excluding vendored, grammars, tree-sitter runtime) LINT_SRCS = $(FOUNDATION_SRCS) $(STORE_SRCS) $(CYPHER_SRCS) $(MCP_SRCS) \ $(DISCOVER_SRCS) $(GRAPH_BUFFER_SRCS) $(PIPELINE_SRCS) $(SIMHASH_SRCS) $(SEMANTIC_SRCS) \ $(TRACES_SRCS) $(WATCHER_SRCS) $(CLI_SRCS) $(EXTRACTION_SRCS) $(AC_LZ4_SRCS) \ $(ZSTD_SRCS) $(SQLITE_WRITER_SRC) $(MAIN_SRC) LINT_HDRS = $(wildcard src/**/*.h src/*.h $(CBM_DIR)/*.h) LINT_TEST_SRCS = $(ALL_TEST_SRCS) # clang-tidy: deep static analysis (config in .clang-tidy) lint-tidy: @echo "=== clang-tidy ===" @$(CLANG_TIDY) --quiet $(LINT_SRCS) -- $(CFLAGS_COMMON) $(SYSROOT_FLAG) # cppcheck: complementary analysis (config in .cppcheck) lint-cppcheck: @echo "=== cppcheck ===" @$(CPPCHECK) --enable=warning,style,performance,portability \ --std=c11 --language=c \ --suppressions-list=.cppcheck \ --error-exitcode=1 \ --inline-suppr \ --quiet \ --suppress=varFuncNullUB \ --suppress=intToPointerCast \ --suppress=unusedStructMember \ --suppress=nullPointerOutOfMemory \ --suppress=nullPointerArithmeticOutOfMemory \ --suppress=ctunullpointerOutOfMemory \ --suppress='nullPointer:internal/cbm/sqlite_writer.c' \ -Isrc -Ivendored -Ivendored/sqlite3 \ -I$(CBM_DIR) -I$(TS_INCLUDE) \ $(LINT_SRCS) # clang-format: formatting check (config in .clang-format, dry-run = no changes) lint-format: @echo "=== clang-format ===" @$(CLANG_FORMAT) --dry-run --Werror $(LINT_SRCS) $(LINT_HDRS) # Ban ALL NOLINT variants EXCEPT whitelisted NOLINT(misc-no-recursion). # The whitelist is in src/foundation/recursion_whitelist.h with documented reasoning. # Rule: NOLINT(misc-no-recursion) is allowed ONLY on function definitions that are # listed in recursion_whitelist.h. All other NOLINT forms are banned. lint-no-suppress: @echo "=== NOLINT check ===" @if grep -rn 'NOLINT' src/ internal/cbm/*.c internal/cbm/*.h 2>/dev/null \ | grep -v vendored \ | grep -v 'NOLINT(misc-no-recursion)' \ | grep -v 'recursion_whitelist.h'; then \ echo "ERROR: Banned NOLINT comment found in source code."; \ echo "Only NOLINT(misc-no-recursion) is allowed, and only for whitelisted functions."; \ echo "See src/foundation/recursion_whitelist.h for the whitelist."; \ exit 1; \ fi @echo " Checking NOLINT(misc-no-recursion) against whitelist..." @scripts/check-nolint-whitelist.sh # All linters (run with make -j3 lint for parallel execution) lint: lint-tidy lint-cppcheck lint-format lint-no-suppress @echo "=== All linters passed ===" # CI linters (no clang-tidy — platform-dependent, enforced locally via pre-commit) lint-ci: lint-cppcheck lint-format lint-no-suppress @echo "=== CI linters passed ===" # ── Security audit (6 layers) ──────────────────────────────────── # Run all security checks: static audit, binary strings, UI, install, network # Requires: production binary already built (make cbm) security: cbm @echo "=== Running security audit suite ===" scripts/security-audit.sh scripts/security-strings.sh $(BUILD_DIR)/codebase-memory-mcp scripts/security-ui.sh scripts/security-install.sh $(BUILD_DIR)/codebase-memory-mcp scripts/security-network.sh $(BUILD_DIR)/codebase-memory-mcp scripts/security-fuzz.sh $(BUILD_DIR)/codebase-memory-mcp scripts/security-vendored.sh @echo "=== All security checks passed ==="