Files
codebase-memory-mcp/Makefile.cbm
T
Andrew Hundt ec70db30fb feat(git): add reusable snapshot provider
Add a shared git snapshot provider for HEAD, dirty worktree hash, and tracked file count. The provider uses the existing portable command fallback, streams status output with O(1) buffer memory, keeps clean trees unambiguous, and supports non-git fallback without changing incremental defaults.

Migrate watcher polling to the provider with flags so baseline still avoids dirty-status work, normal polls check HEAD plus dirty state, and post-index refresh updates HEAD, dirty hash, and file count together.

Validation: git diff --check; bash scripts/check-source-safety.sh; make -j8 -f Makefile.cbm build/c/test-runner; CBM_ONLY_SUITE=watcher ./build/c/test-runner. Pipeline suite also passed after adding git_snapshot to the shared test runner link before the final dirty-hash clean-state tweak.
Signed-off-by: Andrew Hundt <ATHundt@gmail.com>
2026-07-01 05:32:54 -04:00

950 lines
42 KiB
Makefile

# 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 (auto-signed on macOS)
# make -f Makefile.cbm install # Build + install to INSTALL_DIR (default ~/.local/bin)
# make -f Makefile.cbm clean-c # Remove build artifacts
#
# macOS signing note:
# macOS 25+ enforces ad-hoc code signatures on binaries. Copying a binary
# without re-signing causes immediate SIGKILL at runtime. This Makefile
# runs `codesign --force --sign -` automatically after every build and
# install step on macOS. On Linux and other platforms the step is a no-op.
# 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
# ── 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
# ── Optional libgit2 (faster git history parsing) ────────────────
# Auto-detected via pkg-config. Falls back to popen("git log ...") if absent.
LIBGIT2_CFLAGS := $(shell pkg-config --cflags libgit2 2>/dev/null)
LIBGIT2_LIBS := $(shell pkg-config --libs libgit2 2>/dev/null)
ifneq ($(LIBGIT2_LIBS),)
LIBGIT2_FLAGS = -DHAVE_LIBGIT2 $(LIBGIT2_CFLAGS)
else
LIBGIT2_FLAGS =
LIBGIT2_LIBS =
endif
# ── Platform detection & code signing ───────────────────────────
# macOS 25+ kills unsigned or invalidly-signed binaries with SIGKILL.
# codesign --force --sign - applies an ad-hoc signature (no Apple Developer
# account required). On Linux/other platforms this entire block is a no-op.
UNAME_S := $(shell uname -s)
ifeq ($(UNAME_S),Darwin)
CODESIGN_BIN := $(shell command -v codesign 2>/dev/null)
ifneq ($(CODESIGN_BIN),)
# codesign is available — sign and report
define codesign_binary
@$(CODESIGN_BIN) --force --sign - $(1) 2>&1 && \
echo " ✓ signed $(1) (ad-hoc, macOS 25+ compatible)" || \
{ echo " ✗ WARNING: codesign failed for $(1) — binary may crash on macOS 25+"; true; }
endef
else
# codesign not found — warn but don't fail the build
define codesign_binary
@echo " ✗ WARNING: codesign not found — $(1) may crash on macOS 25+ (install Xcode CLT)"
endef
endif
else
# Non-macOS: signing is a documented no-op
define codesign_binary
@echo " (signing skipped — not macOS)"
endef
endif
# 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) \
$(LIBGIT2_FLAGS)
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)
CFLAGS_TSAN = $(CFLAGS_COMMON) -g -O1 \
-fsanitize=thread -fno-omit-frame-pointer
CXXFLAGS_TSAN = $(CXXFLAGS_COMMON) -g -O1 \
-fsanitize=thread -fno-omit-frame-pointer
# Windows needs ws2_32 (Winsock), psapi (GetProcessMemoryInfo), 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 -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 $(LIBGIT2_LIBS) $(WIN32_LIBS) $(STATIC_FLAGS)
LDFLAGS_TEST = -lm -lstdc++ -lpthread -lz $(SANITIZE) $(LIBGIT2_LIBS) $(WIN32_LIBS)
LDFLAGS_TSAN = -lm -lstdc++ -lpthread -lz -fsanitize=thread $(LIBGIT2_LIBS) $(WIN32_LIBS)
# nosan: no ASan/UBSan — required for macOS 'leaks' tool (incompatible with ASan malloc replacement)
CFLAGS_NOSAN = $(CFLAGS_COMMON) -g -O1
LDFLAGS_NOSAN = -lm -lstdc++ -lpthread -lz $(LIBGIT2_LIBS) $(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
# 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
LSP_INCLUDED_SRCS = $(wildcard $(CBM_DIR)/lsp/*.c $(CBM_DIR)/lsp/*.h \
$(CBM_DIR)/lsp/generated/*.c $(CBM_DIR)/lsp/generated/*.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
# 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/pipeline_delta.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_normalize.c \
src/pipeline/httplink.c \
src/pipeline/pass_httplinks.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
# nomic-embed-code pretrained vectors (assembler blob)
UNIXCODER_BLOB_SRC = vendored/nomic/code_vectors_blob.S
# Depindex module (dependency/reference API indexing)
DEPINDEX_SRCS = src/depindex/depindex.c
# PageRank module (node + edge ranking)
PAGERANK_SRCS = src/pagerank/pagerank.c
# 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_command.c \
src/git/git_context.c \
src/git/git_snapshot.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
# Mongoose HTTP library (vendored, compiled with relaxed warnings)
MONGOOSE_SRC = vendored/mongoose/mongoose.c
MONGOOSE_CFLAGS = -std=c11 -D_DEFAULT_SOURCE -O2 -w -Ivendored -DMG_ENABLE_LOG=0
MONGOOSE_CFLAGS_TEST = -std=c11 -D_DEFAULT_SOURCE -g -O1 -w -Ivendored -DMG_ENABLE_LOG=0 \
$(SANITIZE)
MONGOOSE_CFLAGS_NOSAN = -std=c11 -D_DEFAULT_SOURCE -g -O1 -w -Ivendored -DMG_ENABLE_LOG=0
MONGOOSE_CFLAGS_TSAN = -std=c11 -D_DEFAULT_SOURCE -g -O1 -w -Ivendored -DMG_ENABLE_LOG=0 \
-fsanitize=thread -fno-omit-frame-pointer
# 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 <dir>` 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 <regex.h>)
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) $(DEPINDEX_SRCS) $(PAGERANK_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
TEST_EXTRACTION_SRCS = \
tests/test_extraction.c \
tests/test_extraction_inheritance.c \
tests/test_extraction_imports.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_discover.c
TEST_GRAPH_BUFFER_SRCS = tests/test_graph_buffer.c
TEST_PIPELINE_SRCS = tests/test_registry.c tests/test_pipeline.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
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_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_DEPINDEX_SRCS = tests/test_depindex.c
TEST_PAGERANK_SRCS = tests/test_pagerank.c
TEST_TOKEN_REDUCTION_SRCS = tests/test_token_reduction.c
TEST_TOOL_CONSOLIDATION_SRCS = tests/test_tool_consolidation.c
TEST_INPUT_VALIDATION_SRCS = tests/test_input_validation.c
TEST_FQN_SRCS = tests/test_fqn.c
TEST_SECURITY_SRCS = tests/test_security.c
TEST_YAML_SRCS = tests/test_yaml.c
TEST_HTTPLINK_SRCS = tests/test_httplink.c
TEST_SIMHASH_SRCS = tests/test_simhash.c
TEST_STACK_OVERFLOW_SRCS = tests/test_stack_overflow.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_HTTPLINK_SRCS) $(TEST_CLI_SRCS) $(TEST_MEM_SRCS) $(TEST_UI_SRCS) $(TEST_HTTPD_SRCS) $(TEST_DEPINDEX_SRCS) $(TEST_PAGERANK_SRCS) $(TEST_TOKEN_REDUCTION_SRCS) $(TEST_TOOL_CONSOLIDATION_SRCS) $(TEST_INPUT_VALIDATION_SRCS) $(TEST_FQN_SRCS) $(TEST_SECURITY_SRCS) $(TEST_YAML_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) \
-fsanitize=thread -fno-omit-frame-pointer
# 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
# ── Targets ──────────────────────────────────────────────────────
.PHONY: test test-foundation test-tsan test-leak test-analyze test-memory test-gmalloc cbm cbm-with-ui frontend embed clean-c lint lint-tidy lint-cppcheck lint-format lint-source-safety install test-runner-nosan 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: $(CBM_DIR)/lsp_all.c $(LSP_INCLUDED_SRCS) | $(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 $@ $<
# ── Full test binary ─────────────────────────────────────────────
# mimalloc object files
MIMALLOC_OBJ_TEST = $(BUILD_DIR)/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)/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_PROD = $(BUILD_DIR)/prod_sqlite3.o
$(BUILD_DIR)/sqlite3.o: $(SQLITE3_SRC) | $(BUILD_DIR)
$(CC) $(SQLITE3_CFLAGS_TEST) $(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 <regex.h>)
TRE_OBJ_TEST :=
TRE_OBJ_PROD :=
ifeq ($(IS_MINGW),yes)
TRE_OBJ_TEST = $(BUILD_DIR)/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)/prod_tre.o: $(TRE_SRC) | $(BUILD_DIR)
$(CC) $(TRE_CFLAGS) -O2 -c -o $@ $<
endif
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) $(MONGOOSE_OBJ_TEST)
# ── Nosan build: ASan-free test runner for macOS heap leak detection ─────────
#
# WHY THIS EXISTS:
# 'make test-leak' uses Apple's 'leaks --atExit' tool to find heap leaks.
# But leaks cannot inspect a process that uses a custom malloc (such as ASan).
# The regular test-runner is built with -fsanitize=address,undefined, which
# replaces malloc → leaks aborts with "unable to inspect heap ranges".
#
# HOW IT WORKS:
# We rebuild all ASan-instrumented vendored objects without -fsanitize flags
# into $(NOSAN_DIR), then link test-runner-nosan against them.
# The resulting binary runs the full test suite under Apple's heap profiler.
# Full leak report is written to $(LEAK_LOG) = build/c/leak-report.txt.
#
# HOW TO USE:
# make test-leak # runs full suite + heap check, saves report to LEAK_LOG
# cat build/c/leak-report.txt # review complete leak report after run
#
# WHICH OBJECTS NEED NOSAN VARIANTS (use SANITIZE in their *_TEST flags):
# sqlite3, lsp_all, preprocessor, grammar/*.c, ts_runtime
# WHICH ARE REUSED AS-IS (never use SANITIZE):
# mimalloc (MIMALLOC_CFLAGS_TEST has no -fsanitize)
# tre (only on Windows; TRE_CFLAGS has no -fsanitize)
#
NOSAN_DIR = $(BUILD_DIR)/nosan
GRAMMAR_CFLAGS_NOSAN = -std=c11 -D_DEFAULT_SOURCE -g -O1 -w -I$(CBM_DIR) -I$(TS_INCLUDE) -I$(TS_SRC)
GRAMMAR_OBJS_NOSAN = $(patsubst $(CBM_DIR)/%.c,$(NOSAN_DIR)/%.o,$(GRAMMAR_SRCS))
$(NOSAN_DIR):
mkdir -p $(NOSAN_DIR)
# Grammar C files (tree-sitter parsers) — recompiled without ASan/UBSan
$(NOSAN_DIR)/%.o: $(CBM_DIR)/%.c | $(NOSAN_DIR)
$(CC) $(GRAMMAR_CFLAGS_NOSAN) -c -o $@ $<
$(NOSAN_DIR)/ts_runtime.o: $(CBM_DIR)/ts_runtime.c | $(NOSAN_DIR)
$(CC) $(GRAMMAR_CFLAGS_NOSAN) -c -o $@ $<
$(NOSAN_DIR)/lsp_all.o: $(CBM_DIR)/lsp_all.c $(LSP_INCLUDED_SRCS) | $(NOSAN_DIR)
$(CC) $(GRAMMAR_CFLAGS_NOSAN) -c -o $@ $<
$(NOSAN_DIR)/preprocessor.o: $(CBM_DIR)/preprocessor.cpp | $(NOSAN_DIR)
$(CXX) $(CXXFLAGS_COMMON) -g -O1 -w -I$(CBM_DIR)/vendored -c -o $@ $<
$(NOSAN_DIR)/sqlite3.o: $(SQLITE3_SRC) | $(NOSAN_DIR)
$(CC) $(SQLITE3_CFLAGS_TEST) -c -o $@ $<
OBJS_VENDORED_NOSAN = $(MIMALLOC_OBJ_TEST) $(NOSAN_DIR)/sqlite3.o $(TRE_OBJ_TEST) \
$(GRAMMAR_OBJS_NOSAN) $(NOSAN_DIR)/ts_runtime.o \
$(NOSAN_DIR)/lsp_all.o $(NOSAN_DIR)/preprocessor.o \
$(NOSAN_LZ4_OBJ) $(NOSAN_ZSTD_OBJ) $(UNIXCODER_OBJ)
# Vendored LZ4 (test build)
LZ4_OBJ_TEST = $(BUILD_DIR)/test_lz4.o $(BUILD_DIR)/test_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 $@ $<
# Vendored zstd (test build)
ZSTD_OBJ_TEST = $(BUILD_DIR)/test_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 $@ $<
# Vendored LZ4/zstd (NOSAN build — sanitizer-free so `leaks --atExit` can walk the
# heap on macOS; ASan replaces malloc and is incompatible with the leaks tool).
NOSAN_LZ4_OBJ = $(NOSAN_DIR)/lz4.o $(NOSAN_DIR)/lz4hc.o
NOSAN_ZSTD_OBJ = $(NOSAN_DIR)/zstd.o
$(NOSAN_DIR)/lz4.o: $(CBM_DIR)/vendored/lz4/lz4.c | $(NOSAN_DIR)
$(CC) -std=c11 -D_DEFAULT_SOURCE -g -O1 -w -I$(CBM_DIR) -c -o $@ $<
$(NOSAN_DIR)/lz4hc.o: $(CBM_DIR)/vendored/lz4/lz4hc.c | $(NOSAN_DIR)
$(CC) -std=c11 -D_DEFAULT_SOURCE -g -O1 -w -I$(CBM_DIR)/vendored/lz4 -c -o $@ $<
$(NOSAN_DIR)/zstd.o: $(CBM_DIR)/vendored/zstd/zstd.c | $(NOSAN_DIR)
$(CC) -std=c11 -D_DEFAULT_SOURCE -g -O1 -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)
$(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) -o $@ \
$(ALL_TEST_SRCS) $(PROD_SRCS) \
$(EXTRACTION_SRCS) $(AC_LZ4_SRCS) $(ZSTD_SRCS) $(SQLITE_WRITER_SRC) \
$(OBJS_VENDORED_TEST) \
$(LDFLAGS_TEST)
$(BUILD_DIR)/test-runner-nosan: $(ALL_TEST_SRCS) $(PROD_SRCS) $(EXTRACTION_SRCS) $(AC_LZ4_SRCS) $(ZSTD_SRCS) $(SQLITE_WRITER_SRC) $(OBJS_VENDORED_NOSAN) | $(BUILD_DIR) $(NOSAN_DIR)
$(CC) $(CFLAGS_NOSAN) -o $@ \
$(ALL_TEST_SRCS) $(PROD_SRCS) \
$(EXTRACTION_SRCS) $(AC_LZ4_SRCS) $(ZSTD_SRCS) $(SQLITE_WRITER_SRC) \
$(OBJS_VENDORED_NOSAN) \
$(LDFLAGS_NOSAN)
test: $(BUILD_DIR)/test-runner
cd $(CURDIR) && $(BUILD_DIR)/test-runner
# ── TSan full test ───────────────────────────────────────────────
#
# ThreadSanitizer build for data-race detection in the parallel pipeline.
# Mirrors test-runner but links vendored objects compiled with
# -fsanitize=thread and links the test mimalloc object (MI_OVERRIDE=0 —
# does NOT override malloc, so TSan intercepts the real malloc/free) to
# satisfy the unconditional mi_* calls in src/foundation/mem.c.
#
# Cannot be combined with ASan/UBSan. Uses the system allocator.
TSAN_DIR = $(BUILD_DIR)/tsan
GRAMMAR_OBJS_TSAN = $(patsubst $(CBM_DIR)/%.c,$(TSAN_DIR)/%.o,$(GRAMMAR_SRCS))
MIMALLOC_OBJ_TSAN = $(TSAN_DIR)/mimalloc.o
$(TSAN_DIR):
mkdir -p $(TSAN_DIR)
# mimalloc (MI_OVERRIDE=0: no malloc override → TSan intercepts real malloc)
$(TSAN_DIR)/mimalloc.o: $(MIMALLOC_SRC) | $(TSAN_DIR)
$(CC) $(MIMALLOC_CFLAGS_TEST) -fsanitize=thread -fno-omit-frame-pointer -c -o $@ $<
# sqlite3 (TSan-instrumented)
$(TSAN_DIR)/sqlite3.o: $(SQLITE3_SRC) | $(TSAN_DIR)
$(CC) $(SQLITE3_CFLAGS_TEST) -fsanitize=thread -fno-omit-frame-pointer -c -o $@ $<
# Grammar C files (tree-sitter parsers) — recompiled with TSan
$(TSAN_DIR)/%.o: $(CBM_DIR)/%.c | $(TSAN_DIR)
$(CC) $(GRAMMAR_CFLAGS_TSAN) -c -o $@ $<
$(TSAN_DIR)/ts_runtime.o: $(CBM_DIR)/ts_runtime.c | $(TSAN_DIR)
$(CC) $(GRAMMAR_CFLAGS_TSAN) -c -o $@ $<
$(TSAN_DIR)/lsp_all.o: $(CBM_DIR)/lsp_all.c $(LSP_INCLUDED_SRCS) | $(TSAN_DIR)
$(CC) $(GRAMMAR_CFLAGS_TSAN) -fsanitize=thread -fno-omit-frame-pointer -c -o $@ $<
$(TSAN_DIR)/preprocessor.o: $(CBM_DIR)/preprocessor.cpp | $(TSAN_DIR)
$(CXX) $(CXXFLAGS_COMMON) -g -O1 -fsanitize=thread -fno-omit-frame-pointer -w -I$(CBM_DIR)/vendored -c -o $@ $<
# Vendored LZ4 (TSan build)
$(TSAN_DIR)/lz4.o: $(CBM_DIR)/vendored/lz4/lz4.c | $(TSAN_DIR)
$(CC) -std=c11 -D_DEFAULT_SOURCE -g -O1 -fsanitize=thread -fno-omit-frame-pointer -w -I$(CBM_DIR) -c -o $@ $<
$(TSAN_DIR)/lz4hc.o: $(CBM_DIR)/vendored/lz4/lz4hc.c | $(TSAN_DIR)
$(CC) -std=c11 -D_DEFAULT_SOURCE -g -O1 -fsanitize=thread -fno-omit-frame-pointer -w -I$(CBM_DIR)/vendored/lz4 -c -o $@ $<
# Vendored zstd (TSan build)
$(TSAN_DIR)/zstd.o: $(CBM_DIR)/vendored/zstd/zstd.c | $(TSAN_DIR)
$(CC) -std=c11 -D_DEFAULT_SOURCE -g -O1 -fsanitize=thread -fno-omit-frame-pointer -w -I$(CBM_DIR)/vendored/zstd -c -o $@ $<
OBJS_VENDORED_TSAN = $(MIMALLOC_OBJ_TSAN) $(TSAN_DIR)/sqlite3.o $(TRE_OBJ_TEST) \
$(GRAMMAR_OBJS_TSAN) $(TSAN_DIR)/ts_runtime.o \
$(TSAN_DIR)/lsp_all.o $(TSAN_DIR)/preprocessor.o \
$(TSAN_DIR)/lz4.o $(TSAN_DIR)/lz4hc.o $(TSAN_DIR)/zstd.o $(UNIXCODER_OBJ)
$(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) $(TSAN_DIR)
$(CC) $(CFLAGS_TSAN) -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
@echo "Running ThreadSanitizer build (slow). Reports go to stderr."
cd $(CURDIR) && TSAN_OPTIONS="halt_on_error=0 second_deadlock_stack=1" $(BUILD_DIR)/test-runner-tsan
# ── Leak detection ───────────────────────────────────────────────
# macOS: uses `leaks --atExit` (Apple Clang LSan not available on all versions)
# Linux: ASAN_OPTIONS=detect_leaks=1 (GCC/Clang ASan always includes LSan)
# Note: if false positives appear from system libraries on Linux, create lsan.supp
# and set LSAN_OPTIONS=suppressions=lsan.supp
LEAK_LOG = $(BUILD_DIR)/leak-report.txt
ifeq ($(UNAME_S),Darwin)
# macOS: 'leaks' cannot inspect ASan-instrumented processes (ASan replaces malloc).
# Use test-runner-nosan (no ASan/UBSan) so leaks can walk the heap.
test-leak: $(BUILD_DIR)/test-runner-nosan
@echo "Running heap leak detection via 'leaks --atExit' on nosan build (macOS). May take 2-5 minutes."
@echo "Full report saved to $(LEAK_LOG). Exit 0 = no leaks."
leaks --atExit -- $(BUILD_DIR)/test-runner-nosan 2>&1 | tee $(LEAK_LOG); exit $${PIPESTATUS[0]}
else
test-leak: $(BUILD_DIR)/test-runner
@echo "Running heap leak detection via ASan/LSan (Linux). Full report saved to $(LEAK_LOG). Exit 0 = no leaks."
ASAN_OPTIONS=detect_leaks=1 $(BUILD_DIR)/test-runner 2>&1 | tee $(LEAK_LOG); exit $${PIPESTATUS[0]}
endif
# ── Memory-corruption debug (macOS) ───────────────────────────────
# Catches uninit reads, use-after-free, and overruns that ASan/TSan can miss
# (used to investigate the B1 custom-writer non-deterministic corruption).
# All run the NOSAN binary: ASan replaces malloc, which would defeat these
# libmalloc knobs. Set CBM_ONLY_SUITE=<suite> to target a slow suite.
MEM_LOG = $(BUILD_DIR)/mem-report.txt
ifeq ($(UNAME_S),Darwin)
# MallocScribble=1 → freed memory filled with 0x55 (catches use-after-free).
# MallocPreScribble=1 → freshly-allocated memory filled with 0xAA (catches
# uninitialized reads — the macOS MSan equivalent; makes uninit deterministic).
test-memory: $(BUILD_DIR)/test-runner-nosan
@echo "Running under MallocScribble+MallocPreScribble (macOS nosan): uninit reads -> 0xAA, use-after-free -> 0x55."
@echo "Full report saved to $(MEM_LOG)."
MallocScribble=1 MallocPreScribble=1 $(BUILD_DIR)/test-runner-nosan 2>&1 | tee $(MEM_LOG); exit $${PIPESTATUS[0]}
# Guard Malloc (libgmalloc): a guard page around EVERY allocation → crashes at
# the exact overrun / use-after-free, with a stack trace. Stricter than scribble
# (which only paints bytes); slower and noisier. The decisive memory tool.
test-gmalloc: $(BUILD_DIR)/test-runner-nosan
@echo "Running under Guard Malloc (libgmalloc). Crashes at the exact overrun/UAF. Slow."
@echo "Full report saved to $(MEM_LOG)."
DYLD_INSERT_LIBRARIES=/usr/lib/libgmalloc.dylib $(BUILD_DIR)/test-runner-nosan 2>&1 | tee $(MEM_LOG); exit $${PIPESTATUS[0]}
else
test-memory test-gmalloc: $(BUILD_DIR)/test-runner
@echo "These targets are macOS-only (MallocScribble/libgmalloc). On Linux use 'make test-leak' (ASan/LSan), or build with -fsanitize=memory (MSan) for uninit detection."
endif
# ── Static analysis (Clang analyzer only — GCC has no --analyze flag) ──────
ifeq ($(IS_GCC),no)
test-analyze: $(ALL_TEST_SRCS) $(PROD_SRCS)
@echo "Running Clang static analyzer..."
$(CC) --analyze $(CFLAGS_COMMON) \
$(ALL_TEST_SRCS) $(PROD_SRCS) $(EXTRACTION_SRCS) 2>&1 | \
grep -E "warning:|error:|note:" || echo "No issues found."
else
test-analyze:
@echo "Static analysis skipped: requires Clang (not GCC). Install clang and re-run."
endif
# ── 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: $(CBM_DIR)/lsp_all.c $(LSP_INCLUDED_SRCS) | $(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"
$(call codesign_binary,$(BUILD_DIR)/codebase-memory-mcp)
# ── Install to INSTALL_DIR (default ~/.local/bin) ────────────────
# Re-signs after copy — required on macOS 25+ where cp invalidates the
# existing ad-hoc signature and an unsigned binary gets SIGKILL at startup.
INSTALL_DIR ?= $(HOME)/.local/bin
install: cbm
@echo "Installing to $(INSTALL_DIR)/codebase-memory-mcp ..."
cp $(BUILD_DIR)/codebase-memory-mcp $(INSTALL_DIR)/codebase-memory-mcp
$(call codesign_binary,$(INSTALL_DIR)/codebase-memory-mcp)
@echo "Done. Run: $(INSTALL_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
@bash scripts/check-source-safety.sh
lint-source-safety:
@bash scripts/check-source-safety.sh
@bash scripts/test-source-safety.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 ==="