Files
codebase-memory-mcp/Makefile.cbm
T
Martin Vogel bbd13335f5 feat(pipeline): delta-merge executor for the closure route
The closure route stops loading and rewriting the world. Its executor is
now a dedicated subsystem (pipeline_delta.c + orchestration): CLONE the
live generation (copy-on-write where the filesystem offers it), repair
the closure against the clone, PATCH exactly the repaired node/edge set
in one transaction, and publish through the same sealed-staging finalize
leg as the dump path. No full graph load, no full dump, and the general
indexing pipeline is untouched -- the profiled kernel run put those two
at 187s of a 238s one-file repair whose actual resolution work was 0.6s.

Id discipline carries the design. Node ids are AUTOINCREMENT and never
reused; the small in-RAM graph is pre-seeded with PROXY nodes carrying
their real database ids (SELECT ... ORDER BY id with the id watermark
pinned before each insert), and fresh nodes are numbered above the
previous generation's MAX(id) -- so "id > max_db_id" is the complete,
marker-free definition of what the patch inserts, and every edge
endpoint id is database-valid by construction. The inbound-edge snapshot
and its QN-keyed re-link become indexed SQL; a re-link whose endpoint no
longer exists matches no row, which is full-reindex semantics for
deleted symbols.

Fail-closed throughout: an unexpected reference to an unseeded label
surfaces as a UNIQUE-constraint violation that fails the patch
transaction, and EVERY delta failure discards the stage and returns
FORCE_FULL_REINDEX -- the live database is never touched, so a full
rebuild always self-heals whatever the delta could not do.

FTS policy: nodes_fts is contentless, so purged rows cannot be deleted
individually on existing databases; their rowids can never alias a live
node again (AUTOINCREMENT) and dead entries drop out of the rowid join.
The patch inserts rows for exactly the new nodes through the same
cbm_camel_split tokenizer the wholesale rebuild uses.

The legacy gbuf-based tail reverts to serving only the test-only
force_legacy_partial route; the closure orchestration owns its own
coverage merge, publication race gate, surface-row merge and committed
counts, and publishes with fts_wholesale=false.

Gate: the full convergence suite runs against this executor unchanged --
body-edit graph equality with a fresh full index, removed-definition
dropping the dependent's stale edge, tsconfig-alias retargeting, the
decline matrix, and 510 pipeline/incremental/store/integration tests.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-08-03 21:01:38 +02:00

1189 lines
57 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
# 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 <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
# -Wdate-time: with -Werror, any use of __DATE__/__TIME__/__TIMESTAMP__ fails the
# build. Build-time entropy is how one identical source tree produced a new hash
# on every compile (see the local patch in vendored/mimalloc/src/options.c) —
# once removed, it must not creep back through a new call site.
CFLAGS_COMMON = -std=c11 -D_DEFAULT_SOURCE -D_GNU_SOURCE -Wall -Wextra -Werror \
-Wno-unused-parameter -Wno-sign-compare -Wdate-time \
$(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)
# Test seams are OPT-IN, never opt-out. Some suites drive behaviour that only a
# test should be able to ask for (fork an orphan the watchdog must reap, publish
# a lease-ownership marker). That code has no production caller and reads exactly
# like malware to a generic classifier, so it must not be in a shipped binary.
# Opt-IN means the failure mode of forgetting the flag is a CLEAN binary rather
# than a leaky one — the opposite choice would make every future release depend
# on someone remembering. scripts/test.sh passes TEST_SEAMS=1 for the suites that
# need it; the test-runner always has them.
TEST_SEAM_DEFINE :=
ifeq ($(TEST_SEAMS),1)
TEST_SEAM_DEFINE := -DCBM_ENABLE_TEST_SEAMS=1
endif
# 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 $(TEST_SEAM_DEFINE) $(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
EDITOR_TEST_DEFINES = -DCBM_JSON_LIKE_ENABLE_TEST_API=1 \
-DCBM_TOML_EDIT_ENABLE_TEST_API=1 -DCBM_YAML_ENABLE_TEST_API=1 \
-DCBM_TEXT_EDIT_ENABLE_TEST_API=1 -DCBM_CLI_ENABLE_TEST_API=1 \
-DCBM_DIAGNOSTICS_ENABLE_TEST_API=1 -DCBM_ENABLE_TEST_SEAMS=1
# The build system is the single source of truth for "is this binary
# instrumented": compiler-specific probes (__SANITIZE_ADDRESS__) miss
# clang's feature-check spelling and every non-ASan sanitizer, so the
# trap-UBSan leg once ran NATIVE timing budgets on an instrumented
# binary. Any non-empty SANITIZE ⇒ sanitized budgets everywhere.
ifneq ($(strip $(SANITIZE)),)
SANITIZED_DEFINE = -DCBM_SANITIZED_BUILD=1
else
SANITIZED_DEFINE =
endif
KOTLIN_DEDUP_TEST_DEFINE = -DCBM_KOTLIN_DEDUP_TEST_API=1
CALL_REFERENCE_LOOKUP_TEST_DEFINE = -DCBM_CALL_REFERENCE_LOOKUP_TEST_API=1
INCREMENTAL_TEST_DEFINE = -DCBM_INCREMENTAL_TEST_API=1
CFLAGS_TEST = $(CFLAGS_COMMON) $(EDITOR_TEST_DEFINES) $(SANITIZED_DEFINE) \
$(KOTLIN_DEDUP_TEST_DEFINE) $(CALL_REFERENCE_LOOKUP_TEST_DEFINE) \
$(INCREMENTAL_TEST_DEFINE) -g -O1 $(SANITIZE)
CXXFLAGS_TEST = $(CXXFLAGS_COMMON) $(SANITIZED_DEFINE) -g -O1 $(SANITIZE) $(CXX_STDLIB_FLAGS)
# TSan (can't combine with ASan)
TSAN_SANITIZE = -fsanitize=thread -fno-omit-frame-pointer
CFLAGS_TSAN = $(CFLAGS_COMMON) $(EDITOR_TEST_DEFINES) $(KOTLIN_DEDUP_TEST_DEFINE) \
$(CALL_REFERENCE_LOOKUP_TEST_DEFINE) $(INCREMENTAL_TEST_DEFINE) \
-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
# advapi32 (owner-only activation-candidate ACLs), plus
# --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 :=
# Route Windows allocations through mimalloc (#581). mimalloc's own static
# override is gated on _MSC_VER, which clang/MinGW never defines, so it
# compiled out and the CRT won the link: ordinary malloc went to the CRT heap,
# which keeps freed pages committed, leaving every allocator tuning in
# cbm_mem_init POSIX-only. --wrap redirects the same population of allocations
# that link-order override captures on POSIX, so the purge configuration
# finally applies here too. src/foundation/mem_override_win.c supplies the
# wrappers; its deallocating paths check mi_is_in_heap_region so a CRT-owned
# pointer can never reach mi_free (#424).
MIMALLOC_WRAP_SYMS := malloc calloc realloc free strdup strndup _msize \
_aligned_malloc _aligned_free
MIMALLOC_WRAP_FLAGS :=
ifeq ($(IS_MINGW),yes)
MIMALLOC_WRAP_FLAGS := $(foreach sym,$(MIMALLOC_WRAP_SYMS),-Wl,--wrap=$(sym))
endif
# Linux wraps a smaller set for MEASUREMENT only (see mem_override_posix.c):
# allocations already reach mimalloc here, but the profiler needs the same
# observation point as Windows or the platform comparison is not like-for-like.
# macOS ld has no --wrap, so it stays census-only.
IS_LINUX := $(shell uname -s 2>/dev/null | grep -q Linux && echo yes || echo no)
MIMALLOC_WRAP_SYMS_POSIX := malloc calloc realloc free strdup
MIMALLOC_WRAP_FLAGS_POSIX :=
ifeq ($(IS_LINUX),yes)
MIMALLOC_WRAP_FLAGS_POSIX := $(foreach sym,$(MIMALLOC_WRAP_SYMS_POSIX),-Wl,--wrap=$(sym))
endif
ifeq ($(IS_MINGW),yes)
# --no-insert-timestamp: the PE header otherwise carries the link wall clock, so
# two Windows builds of identical source are never byte-identical and every
# release is a brand-new file to a reputation system. Same motivation as the
# mimalloc __DATE__ patch; ELF and Mach-O have no equivalent field to clear.
WIN32_LIBS := -lws2_32 -lpsapi -lshell32 -ladvapi32 -Wl,--allow-multiple-definition -Wl,--stack,8388608 -Wl,--no-insert-timestamp -static
endif
# STATIC=1 produces a fully static binary (for Alpine/musl portable builds)
ifeq ($(STATIC),1)
STATIC_FLAGS := -static
endif
# W^X: demand a non-executable stack on ELF. This is belt to the braces of the
# .note.GNU-stack annotation in vendored/nomic/code_vectors_blob.S — that note
# fixes the CAUSE (an unannotated object makes ld assume the worst for the whole
# link), this flag fixes the OUTCOME, and the composition gate proves it on the
# shipped artifact. ELF-only: Apple's ld rejects -z noexecstack outright and it
# is meaningless for PE, so it is gated rather than made "common".
ELF_HARDENING_FLAGS :=
ifeq ($(IS_LINUX),yes)
ELF_HARDENING_FLAGS := -Wl,-z,noexecstack
endif
# The POSIX wrap shim exists only so the profiler can observe allocations. It
# must never reach a sanitized build: the Linux/macOS test builds are CRT+ASan,
# and redirecting malloc into mimalloc underneath ASan's own interception mixes
# two allocators on the same pointers. Windows keeps its wrap flags everywhere,
# because there the shim is what makes mimalloc own the allocations at all.
# C++ stdlib selection: MSan builds must link an MSan-instrumented libc++
# instead of the system libstdc++ (scripts/msan.sh overrides both vars).
CXX_STDLIB ?= -lstdc++
CXX_STDLIB_FLAGS ?=
LDFLAGS = -lm $(CXX_STDLIB) -lpthread -lz $(WIN32_LIBS) $(STATIC_FLAGS) $(MIMALLOC_WRAP_FLAGS) $(ELF_HARDENING_FLAGS)
LDFLAGS_TEST = -lm $(CXX_STDLIB) -lpthread -lz $(SANITIZE) $(WIN32_LIBS) $(MIMALLOC_WRAP_FLAGS) $(ELF_HARDENING_FLAGS)
LDFLAGS_TSAN = -lm $(CXX_STDLIB) -lpthread -lz $(TSAN_SANITIZE) $(WIN32_LIBS) $(MIMALLOC_WRAP_FLAGS) $(ELF_HARDENING_FLAGS)
# ── Source files ─────────────────────────────────────────────────
FOUNDATION_SRCS = \
src/foundation/mem_override_win.c \
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 \
src/foundation/macos_acl.c \
src/foundation/private_file_lock.c \
src/foundation/lock_registry.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)/macro_table.c \
$(CBM_DIR)/iris_export_xml.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
TS_RUNTIME_DEPS = \
$(TS_RUNTIME_SRC) \
$(wildcard $(CBM_DIR)/vendored/ts_runtime/include/tree_sitter/*.h) \
$(wildcard $(CBM_DIR)/vendored/ts_runtime/src/*.c) \
$(wildcard $(CBM_DIR)/vendored/ts_runtime/src/*.h) \
$(wildcard $(CBM_DIR)/vendored/ts_runtime/src/portable/*.h) \
$(wildcard $(CBM_DIR)/vendored/ts_runtime/src/unicode/*.h)
# 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
# Shared MCP daemon coordination and local transport
DAEMON_SRCS = \
src/daemon/daemon.c \
src/daemon/project_lock.c \
src/daemon/version_cohort.c \
src/daemon/service.c \
src/daemon/runtime.c \
src/daemon/application.c \
src/daemon/frontend.c \
src/daemon/host.c \
src/daemon/bootstrap.c \
src/daemon/ipc.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/lsp_surface.c \
src/pipeline/pipeline_delta.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 \
src/cli/agent_clients.c src/cli/agent_profiles.c \
src/cli/config_json_like.c src/cli/config_toml_edit.c src/cli/config_yaml_edit.c \
src/cli/config_text_edit.c src/cli/activation_transaction.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 <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).
#
# SQLITE_OMIT_LOAD_EXTENSION: no cbm code calls sqlite3_load_extension or
# sqlite3_enable_load_extension, and we never want a graph DB to be able to pull
# a shared library into the process. Loading is off by default at runtime, but
# "disabled by default" is a setting while "not compiled in" is a property —
# this compiles the machinery out, removing the API surface and part of the
# dlopen/dlsym import surface with it. The composition gate asserts the symbols
# are absent from every release artifact.
SQLITE3_OMIT_FLAGS = -DSQLITE_OMIT_LOAD_EXTENSION
SQLITE3_SRC = vendored/sqlite3/sqlite3.c
SQLITE3_CFLAGS = -std=c11 -O2 -w -DSQLITE_DQS=0 -DSQLITE_THREADSAFE=1 -DSQLITE_ENABLE_FTS5 \
$(SQLITE3_OMIT_FLAGS)
SQLITE3_CFLAGS_TEST = -std=c11 -g -O1 -w -DSQLITE_DQS=0 -DSQLITE_THREADSAFE=1 -DSQLITE_ENABLE_FTS5 \
$(SQLITE3_OMIT_FLAGS)
# 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) $(DAEMON_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)
# Project headers, as prerequisites for the targets built in one compiler
# invocation from sources (test-runner, test-repro-runner, test-runner-tsan,
# codebase-memory-mcp, test-foundation). Those rules list only .c files, so
# editing a header changed nothing make could see and the target was NOT
# rebuilt — an incremental build then silently tested the OLD header value.
# Found while revert-checking a constants.h change: the check passed because
# nothing had recompiled.
#
# Vendored trees are deliberately excluded — they are pinned, they carry their
# own explicit dep lists (LSP_UNITY_DEPS, TS_RUNTIME_DEPS), and globbing tens of
# thousands of grammar headers would cost more than it protects.
PROJECT_HDRS = $(wildcard src/*.h src/*/*.h $(CBM_DIR)/*.h $(CBM_DIR)/lsp/*.h tests/*.h \
tests/repro/*.h)
# ── 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_diagnostics.c \
tests/test_dump_verify.c \
tests/test_subprocess.c \
tests/test_private_file_lock.c \
tests/test_lock_registry.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 \
tests/test_index_supervisor.c
TEST_DAEMON_SRCS = \
tests/test_daemon.c \
tests/test_project_lock.c \
tests/test_version_cohort.c \
tests/test_daemon_version.c \
tests/test_daemon_runtime.c \
tests/test_daemon_application.c \
tests/test_daemon_frontend.c \
tests/test_daemon_bootstrap.c \
tests/test_daemon_ipc.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_cross_repo.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 tests/test_call_reference_contract.c tests/repro/repro_call_scope_usages.c tests/repro/repro_call_argument_usages.c tests/repro/repro_reference_precision.c tests/repro/repro_lexical_binding_precision.c tests/repro/repro_call_argument_matrix_a.c tests/repro/repro_call_argument_matrix_b.c tests/repro/repro_call_node_behaviors.c tests/repro/repro_language_registry.c tests/repro/repro_call_node_manifest.c tests/repro/repro_lsp_ordered_signatures.c tests/repro/repro_lsp_ordered_local.c tests/repro/repro_ts_overload_return_chains.c tests/repro/repro_harness_cleanup.c tests/repro/repro_runner_filter.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 tests/test_agent_clients.c tests/test_agent_profiles.c \
tests/test_config_json_like.c \
tests/test_config_toml_edit.c tests/test_config_yaml_edit.c tests/test_config_text_edit.c \
tests/test_activation_transaction.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).
# Contains RED reproductions plus GREEN controls — 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_runner_filter.c \
tests/repro/repro_harness_cleanup.c \
tests/repro/repro_language_registry.c \
tests/repro/repro_call_node_manifest.c \
tests/repro/repro_call_scope_usages.c \
tests/repro/repro_call_argument_usages.c \
tests/repro/repro_lsp_ordered_signatures.c \
tests/repro/repro_lsp_ordered_local.c \
tests/repro/repro_ts_overload_return_chains.c \
tests/repro/repro_reference_precision.c \
tests/repro/repro_lexical_binding_precision.c \
tests/repro/repro_call_argument_matrix_a.c \
tests/repro/repro_call_argument_matrix_b.c \
tests/repro/repro_call_node_behaviors.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_DAEMON_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-par test-repro test-foundation test-tsan test-daemon-smoke 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) $(PROJECT_HDRS) | $(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: $(TS_RUNTIME_DEPS) | $(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: $(TS_RUNTIME_DEPS) | $(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 $@ $<
# static.c is an AMALGAMATION: it #includes options.c and friends, none of which
# make can see through the single named prerequisite, and the compile flags live
# in this Makefile. Both gaps bit for real — the __DATE__ removal in options.c and
# SQLITE_OMIT_LOAD_EXTENSION below each silently did nothing on an incremental
# build because the object was considered up to date. Depend on the included
# sources AND on this Makefile so a flag change rebuilds too.
$(BUILD_DIR)/prod_mimalloc.o: $(MIMALLOC_SRC) $(wildcard vendored/mimalloc/src/*.c) \
$(wildcard vendored/mimalloc/include/*.h) Makefile.cbm | $(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) Makefile.cbm | $(BUILD_DIR)
$(CC) $(SQLITE3_CFLAGS) -c -o $@ $<
# TRE regex (only compiled on Windows — POSIX uses system <regex.h>)
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
# NOTE: tre.o once dropped UBSan's alignment check here. That was a real
# coverage hole in a vendored regex engine we ship. Re-enabled: if the
# upstream misaligns, we want the report, not silence.
$(BUILD_DIR)/tre.o: $(TRE_SRC) | $(BUILD_DIR)
$(CC) $(TRE_CFLAGS) $(SANITIZE) -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_EXTRA_CFLAGS: hook for sanitizer lanes. The MSan lane needs
# `-include stdint.h` for THIS object only: zstd's MEMORY_SANITIZER-guarded
# block declares __msan_test_shadow returning intptr_t, but the amalgamator
# collapsed the `#define ZSTD_DEPS_NEED_STDINT` re-include that should supply
# it. The flag cannot go on the global SANITIZE line: force-including a libc
# header ahead of a source file freezes glibc's feature-test macros before
# files like sqlite3.c set _GNU_SOURCE themselves, which strips
# MREMAP_MAYMOVE/nanosleep out of their view of libc.
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) $(ZSTD_EXTRA_CFLAGS) -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) $(PROJECT_HDRS) | $(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
# Parallel variant: every suite as its own process; identical gate quality
# (see the ZERO-LOSS CONTRACT in scripts/run-tests-parallel.sh — union guard
# against --list-suites + aggregated pass/fail/skip totals).
test-par: $(BUILD_DIR)/test-runner
cd $(CURDIR) && bash scripts/run-tests-parallel.sh $(BUILD_DIR)/test-runner
# Focused native development is intentionally a separate, explicit target so
# an inherited TEST_SUITES value cannot silently narrow the gating test target.
TEST_SUITES ?=
test-focused: $(BUILD_DIR)/test-runner
@test -n "$(strip $(TEST_SUITES))" || \
(echo "TEST_SUITES is required for test-focused"; exit 2)
cd $(CURDIR) && $(BUILD_DIR)/test-runner $(TEST_SUITES)
# ── Cumulative bug-reproduction runner (RED board 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) $(PROJECT_HDRS) | $(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) && bash tests/repro/repro_script_summary.sh
cd $(CURDIR) && bash tests/repro/repro_script_zero_selection.sh
cd $(CURDIR) && bash tests/repro/repro_script_missing_summary.sh
cd $(CURDIR) && bash tests/repro/repro_script_skipped_summary.sh
cd $(CURDIR) && bash tests/repro/repro_runner_zero_selection.sh
cd $(CURDIR) && bash tests/repro/repro_runner_passing_semantics.sh
cd $(CURDIR) && bash tests/repro/repro_script_no_workspace_artifact.sh
cd $(CURDIR) && bash tests/repro/repro_script_stderr_summary.sh
cd $(CURDIR) && bash tests/repro/repro_make_tracks_headers.sh
cd $(CURDIR) && $(BUILD_DIR)/test-repro-runner
# ── TSan full test ───────────────────────────────────────────────
# Every threaded production surface that runs clean AND stable under TSan:
# allocator concurrency (mem, slab_alloc), the parallel extraction worker pool
# (parallel, worker_pool, pipeline), the filesystem watcher (watcher), the
# embedded HTTP server (httpd), diagnostics sampling (diagnostics), the MCP
# server + mutation guard (mcp, mcp_mutation_guard), subprocess supervision
# (subprocess), and the runnable daemon-coordination paths (daemon,
# daemon_application). Was `mem slab_alloc parallel` — a keyhole that ran TSan
# over no real threaded production code.
#
# NO EXCLUSIONS. A sanitizer covers the complete surface or it is asserting
# coverage it does not have. The three suites once excluded here
# (daemon_runtime, daemon_ipc, daemon_frontend) are back:
# daemon_ipc, daemon_frontend — the documented test-harness race and thread
# leaks no longer reproduce; both run clean under TSan.
# daemon_runtime — did NOT deadlock as documented. It reported a REAL
# production data race on the log sink (plain global function pointer,
# written by the configuring thread, read AND CALLED by connection
# workers), now fixed with atomics in log.c. Excluding the suite is what
# had hidden it.
TEST_TSAN_SUITES ?= mem slab_alloc parallel worker_pool watcher httpd pipeline \
diagnostics mcp mcp_mutation_guard subprocess daemon daemon_application \
daemon_runtime daemon_ipc daemon_frontend
# halt_on_error: a race is a bug, stop at the first one.
#
# report_thread_leaks=0: this disables TSan's thread-HYGIENE check only —
# RACE detection, the reason the lane exists, is completely unaffected. Some
# daemon fixtures fork after the process has gone multi-threaded, and in the
# forked child TSan sees the parent's finished threads as never-joined even
# though the fixture joins them (test_daemon_frontend.c:726 joins the thread
# reported at :698). It fires on macOS and not Linux, i.e. it tracks fork
# semantics rather than anything about our code. The alternative was dropping
# whole suites from the lane, which costs real race coverage; this costs none.
TSAN_OPTIONS ?= halt_on_error=1:report_thread_leaks=0
# A fixed concurrent envelope keeps TSan deterministic across developer hosts
# and GitHub runners. Four workers still exercise real races without turning
# sanitizer-instrumented allocator bookkeeping into a high-core lock convoy.
# Normal ASan/native/soak paths remain uncapped. High-worker TSan diagnostics
# invoke test-runner-tsan directly so release gates cannot drift accidentally.
$(BUILD_DIR)/test-runner-tsan: $(ALL_TEST_SRCS) $(PROD_SRCS) $(EXTRACTION_SRCS) $(AC_LZ4_SRCS) $(ZSTD_SRCS) $(SQLITE_WRITER_SRC) $(OBJS_VENDORED_TSAN) $(PROJECT_HDRS) | $(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
@echo "ThreadSanitizer workers: 4"
cd $(CURDIR) && CBM_WORKERS=4 TSAN_OPTIONS="$(TSAN_OPTIONS)" \
$(BUILD_DIR)/test-runner-tsan $(TEST_TSAN_SUITES)
# Real-binary POSIX lifecycle smoke. The endpoint is deliberately account-wide;
# an explicit Make invocation requires a clean rendezvous and fails rather than
# silently skipping an occupied one. Deterministic C tests cover Windows IPC.
test-daemon-smoke: $(BUILD_DIR)/codebase-memory-mcp
cd $(CURDIR) && CBM_DAEMON_SMOKE_REQUIRE_RUN=1 python3 tests/test_daemon_smoke.py $(BUILD_DIR)/codebase-memory-mcp
# ── 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: $(TS_RUNTIME_DEPS) | $(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
# Rebuild when the build CONFIGURATION changes, not only when sources do. The
# product binary is compiled in ONE shot from sources, so after a TEST_SEAMS or
# version flip make finds the binary newer than every source and skips the recipe
# entirely, handing back a binary built with the PREVIOUS configuration. The
# dangerous direction is a release build silently keeping test seams, which is a
# property no reviewer can see by reading the diff. The stamp's CONTENT is the
# configuration and it is rewritten only when that content changes, so ordinary
# incremental builds are unaffected. (Single-quoted: the signature may contain
# the escaped quotes of -DCBM_VERSION, which are literal inside '', but must not
# contain a literal single quote.)
BUILD_CONFIG_SIG := TEST_SEAMS=$(TEST_SEAMS)|CFLAGS_EXTRA=$(CFLAGS_EXTRA)
.PHONY: build-config-check
build-config-check:
$(BUILD_DIR)/.build-config: build-config-check | $(BUILD_DIR)
@printf '%s\n' '$(BUILD_CONFIG_SIG)' > $@.tmp
@if cmp -s $@.tmp $@; then rm -f $@.tmp; \
else mv -f $@.tmp $@; rm -f $(BUILD_DIR)/codebase-memory-mcp; \
echo "=== build config changed -> full rebuild: $(BUILD_CONFIG_SIG) ==="; fi
$(BUILD_DIR)/codebase-memory-mcp: $(MAIN_SRC) $(PROD_SRCS) $(EXTRACTION_SRCS) $(AC_LZ4_SRCS) $(ZSTD_SRCS) $(SQLITE_WRITER_SRC) $(OBJS_VENDORED_PROD) $(BUILD_DIR)/.build-config $(PROJECT_HDRS) | $(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: export BUILD_DIR := $(BUILD_DIR)
clean-c:
@bash -c 'source scripts/path-safety.sh && cbm_remove_build_dir "$$PWD" "$$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) $(DAEMON_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 ==="
# ── Local memory-diagnostic lanes (not PR-CI gates by decision: the diag
# build doubles a runner's bill for a marginal delta, and a gating analyzer
# lane needs a suppression story the NOLINT ban deliberately forbids.
# Run these on the local ladder; both found real bugs on first use.) ──
# Newest pinned toolchain + ASan/UBSan + straighter stacks. Separate BUILD_DIR
# so the shipping-toolchain build stays untouched.
#
# This lane also turns on the ASan checks that are OFF by default everywhere
# else, which is the difference between running ASan and running all of it:
# detect_stack_use_after_return a returned-to stack frame reused later
# detect_stack_use_after_scope a pointer outliving its enclosing block
# strict_string_checks str* reads validated over the whole buffer
#
# detect_invalid_pointer_pairs is deliberately NOT here. It fires during
# static initialisation inside vendored simplecpp -- a std::string global at
# simplecpp.cpp:101 -- reporting a pair whose second "pointer" is the
# sentinel 0xfffffffffffffff3, i.e. libstdc++ string internals rather than
# anything this codebase wrote. The option is a process-wide runtime flag
# with no per-file scoping, so it cannot be aimed away from vendored code the
# way the analyzer's path filter can. Enabling it would mean a permanently
# red lane reporting a non-defect, which teaches people to ignore the lane.
# The instrumentation it requires (-fsanitize=pointer-compare,pointer-
# subtract) comes out with it, since it is dead weight without the check.
# They stay on the diagnostic lane rather than the gating ones until they have
# a clean history here; promoting them is a separate, deliberate step.
DIAG_LLVM_BIN ?= /opt/homebrew/opt/llvm/bin
diag:
@echo "=== diagnostic lane: $(shell $(DIAG_LLVM_BIN)/clang --version | head -1) ==="
$(MAKE) -f Makefile.cbm build/diag/test-runner \
CC=$(DIAG_LLVM_BIN)/clang CXX=$(DIAG_LLVM_BIN)/clang++ BUILD_DIR=build/diag \
SANITIZE="-fsanitize=address,undefined -fno-omit-frame-pointer -fno-optimize-sibling-calls"
ASAN_OPTIONS="detect_stack_use_after_return=1:strict_string_checks=1:detect_stack_use_after_scope=1" \
./build/diag/test-runner
# ── macOS leak detection ─────────────────────────────────────────
# LeakSanitizer is ON by default under ASan on Linux, so the Linux test leg
# has had leak coverage all along and the Linux-only leaks this branch fixed
# were found there. On macOS it is OFF by default and Apple's clang refuses
# detect_leaks=1 outright ("Exiting: detect_leaks is not supported on this
# platform"), which is why this repo had NO macOS leak coverage — a whole
# platform's worth of the sanitizer matrix missing.
#
# Apple's refusal is not a darwin limitation. Upstream clang supports LSan on
# darwin/arm64: this lane runs the full suite clean and was verified to still
# catch a deliberately leaked allocation. So the lane is simply "the normal
# ASan test build, compiled by Homebrew LLVM instead of Apple clang, run with
# leak detection turned on".
LSAN_LLVM_BIN ?= $(shell brew --prefix llvm 2>/dev/null)/bin
LSAN_SUITES ?=
test-lsan:
@echo "=== macOS leak lane: $(shell $(LSAN_LLVM_BIN)/clang --version | head -1) ==="
$(MAKE) -f Makefile.cbm build/lsan/test-runner \
CC=$(LSAN_LLVM_BIN)/clang CXX=$(LSAN_LLVM_BIN)/clang++ BUILD_DIR=build/lsan
cd $(CURDIR) && ASAN_OPTIONS="detect_leaks=1:halt_on_error=1" \
./build/lsan/test-runner $(LSAN_SUITES)
# Path-sensitive memory analysis: leak paths, null derefs, uninitialized reads.
# Non-gating: triage findings against the code (this lane's first run produced
# 9 real fixes and 6 recorded false positives — see the analyzer batch commit).
LINT_MEM_CHECKS = -*,clang-analyzer-unix.Malloc,clang-analyzer-unix.MallocSizeof,clang-analyzer-core.NullDereference,clang-analyzer-core.CallAndMessage,clang-analyzer-core.UndefinedBinaryOperatorResult,clang-analyzer-core.uninitialized.*,clang-analyzer-cplusplus.NewDelete,clang-analyzer-cplusplus.NewDeleteLeaks
lint-mem:
@echo "=== clang-analyzer memory lane (non-gating; triage, don't suppress) ==="
@printf '%s\n' $(LINT_SRCS) | xargs -P 10 -I{} $(CLANG_TIDY) --quiet \
--checks='$(LINT_MEM_CHECKS)' {} -- $(CFLAGS_COMMON) $(SYSROOT_FLAG) 2>/dev/null; true
# Gating variant: identical run, but vendored-tree diagnostics are excluded
# (analyzer diagnostics bypass --header-filter by design; this mirrors the
# .cppcheck vendored suppression) and any finding the gate cannot account for
# fails the target. A finding here is a leak path, null deref, or uninitialized
# read the analyzer can prove — triage it against the code.
#
# A finding is accounted for by being FIXED, or — only where the analyzer is
# genuinely wrong — by an argued entry in scripts/lint-mem-whitelist.txt. Each
# entry is pinned to the sha256 of the function it argues about, so editing
# that function invalidates it and the finding must be argued again against
# the new code. A suppression is never allowed to outlive its reasoning, and
# NOLINT is never honoured here.
lint-mem-ci:
@echo "=== clang-analyzer memory gate ==="
@printf '%s\n' $(LINT_SRCS) | xargs -P 4 -I{} $(CLANG_TIDY) --quiet \
--checks='$(LINT_MEM_CHECKS)' {} -- $(CFLAGS_COMMON) $(SYSROOT_FLAG) 2>/dev/null \
| python3 scripts/lint-mem-gate.py
# ── 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 ==="