Files
deusdata--codebase-memory-mcp/Makefile.cbm
Martin Vogel 1f674c805e harden: remove capability that should never have shipped
Microsoft's ML flagged the rc.1 release binaries. The decisive evidence is that
the SAME sha256 went from 0/62 clean to Microsoft-detected in about an hour with
no byte change, so the verdict lives partly in scanner-side state and no code
change can promise a clean result. What code CAN do is stop shipping things that
have no business in a release artifact, which is worth doing on its own merits
and incidentally widens the classifier margin. Every claim below is verified
against a built binary by the new gate, not by reading source.

Executable stack (the worst of the findings). vendored/nomic/code_vectors_blob.S
is the only assembly in the build and carried no .note.GNU-stack. An unannotated
object makes ld assume the worst for the whole link, so EVERY Linux release we
have ever shipped had GNU_STACK RWE. Adds the note (cause) plus ELF-only
-Wl,-z,noexecstack (outcome); the gate fails the release if it returns.

Test seams are now opt-in, never opt-out. TEST_SEAMS=1 defines
CBM_ENABLE_TEST_SEAMS; without it the crash-orphan probe -- which forks a child
that ignores SIGTERM and loops forever, then writes its pid to a caller-supplied
path -- and the lease-ownership marker compile to trivial stubs, so call sites are
untouched and the binary holds no fork, no signal handler and no env-var string.
Opt-IN is the point: forgetting the flag yields a clean binary rather than a leaky
one. scripts/test.sh requests it in the leg that consumes it, and
tests/test_worker_watchdog.sh now asserts the capability up front instead of
dying later with an opaque "Killed: 9".

The daemon's background version check is gone. It spawned curl against
api.github.com/repos/.../releases/latest on the first eligible session of every
run to say "a newer version exists" -- a release URL and an outbound request in
every shipped binary, for something the install scripts already report. The
INJECTABLE SEAM survives: update_ops is still honoured, the fakes in
tests/test_daemon_application.c still cover notice/ownership/cancellation/replay,
and with no provider application_update_subscribe_locked returns early so no
generation ever starts. "No network request by default" is now structural.

Dead capability out of release builds. The tar.gz/zip extraction block
(gzip_decompress through cbm_extract_binary_from_zip, plus its cli.h
declarations) moves under CBM_CLI_ENABLE_TEST_API -- verified self-contained, zero
uses of any helper outside it, only callers the excluded updater and
tests/test_cli.c. Downloading an archive, decompressing it, picking an executable
out of it and marking it executable is the canonical dropper composite; it is now
absent rather than merely unreachable. SQLite is built with
-DSQLITE_OMIT_LOAD_EXTENSION (no caller of load_extension anywhere in src/ or
internal/), removing that API surface and part of the dlopen/dlsym surface.

Temp files and environment scanning (S2/S3). Predictable paths in mcp.c,
artifact.c and diagnostics.c are created privately and exclusively and written
through the returned descriptor; pass_envscan.c no longer descends symlinked
directories out of the project root, and its fixed 512-byte path buffers no
longer truncate into pointer arithmetic that could land outside the buffer.

Build-time entropy. mimalloc's version banner baked __DATE__/__TIME__ into every
binary, so two builds of identical source seconds apart could never share a hash
and no release could inherit a false-positive determination made about its
predecessor. Local patch removes it (marked to survive refreshes), -Wdate-time
makes any future use a build error, and -Wl,--no-insert-timestamp stops the PE
header carrying the link clock.

scripts/ci/check-binary-composition.sh is the proof that each removal stays
removed, wired into package-release.sh after strip so the local artifact-flow
smoke enforces exactly what CI does. It asserts absences plus a CANARY string, so
handing it a compressed, stubbed or empty file fails instead of passing
vacuously, and a missing tool is a hard error -- a skipped assertion must never
look like a satisfied one.

Two build-system traps found by that gate, both of which had silently defeated a
fix: the product binary is compiled in one shot from sources, so a flag flip did
not rebuild it (now tracked by a .build-config stamp that also removes the
binary, making it independent of mtime granularity); and prod_sqlite3.o /
prod_mimalloc.o depended on a single named source, so SQLITE_OMIT_LOAD_EXTENSION
and the mimalloc patch BOTH compiled to nothing on the first incremental build.
Source review would have called them done.

Deliberately NOT changed. Three seams stay in release artifacts because
scripts/smoke-test.sh runs against the real artifact and needs them:
CBM_TEST_CRASH_ON and CBM_TEST_HANG_ON inject the faults that prove supervisor
recovery, and CBM_TEST_WINDOWS_USER_PATH_RUN_ID is what keeps the PATH smoke from
writing the tester's actual PATH. The gate treats those as an allowlist, so a
NOVEL seam still fails. The true no-UI standard build is deferred rather than
rushed: src/ui/* is in PROD_SRCS and four files outside src/ui reference UI
symbols, including the daemon that serves the UI, so that assertion reports
instead of failing until the split lands -- a gate everyone knows is red teaches
people to ignore gates.

No grammar is removed. ObjectScript accounts for essentially all binary growth
since the last provably-clean release (+21.4MB rodata, +1.1MB text from two
four-line shims), which made it the obvious ablation candidate, but a dry run
performed twelve real Defender endpoint scans across standard/UI and amd64/arm64
with ObjectScript, the daemon and the expanded hooks all present and every scan
was clean. Nothing there is a deterministic trigger, so cutting a
community-contributed language would spend a real feature on unproven margin.
Lean is not a candidate either: at 99.6MB of source it is by far the largest
grammar, but it shipped in v0.9.0 which scanned 20/20 clean, so removing it would
produce a novel unscanned profile instead of restoring a known-good one.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-29 23:50:53 +02:00

1036 lines
47 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
CFLAGS_TEST = $(CFLAGS_COMMON) $(EDITOR_TEST_DEFINES) $(SANITIZED_DEFINE) -g -O1 $(SANITIZE)
CXXFLAGS_TEST = $(CXXFLAGS_COMMON) $(SANITIZED_DEFINE) -g -O1 $(SANITIZE)
# TSan (can't combine with ASan)
TSAN_SANITIZE = -fsanitize=thread -fno-omit-frame-pointer
CFLAGS_TSAN = $(CFLAGS_COMMON) $(EDITOR_TEST_DEFINES) -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.
LDFLAGS = -lm -lstdc++ -lpthread -lz $(WIN32_LIBS) $(STATIC_FLAGS) $(MIMALLOC_WRAP_FLAGS) $(ELF_HARDENING_FLAGS)
LDFLAGS_TEST = -lm -lstdc++ -lpthread -lz $(SANITIZE) $(WIN32_LIBS) $(MIMALLOC_WRAP_FLAGS) $(ELF_HARDENING_FLAGS)
LDFLAGS_TSAN = -lm -lstdc++ -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/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)
# ── 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
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).
# These cases are RED by design (one open bug each) — see tests/repro/repro_main.c.
# Kept out of the gating `make test` so `ci-ok` stays green; run via `make test-repro`.
TEST_REPRO_SRCS = \
tests/repro/repro_main.c \
tests/repro/repro_parallel_determinism.c \
tests/repro/repro_extraction.c \
tests/repro/repro_issue495.c \
tests/repro/repro_issue521.c \
tests/repro/repro_issue382.c \
tests/repro/repro_issue408.c \
tests/repro/repro_issue56.c \
tests/repro/repro_issue480.c \
tests/repro/repro_issue571.c \
tests/repro/repro_issue523.c \
tests/repro/repro_issue546.c \
tests/repro/repro_issue627.c \
tests/repro/repro_issue514.c \
tests/repro/repro_issue510.c \
tests/repro/repro_issue557.c \
tests/repro/repro_issue520.c \
tests/repro/repro_issue333.c \
tests/repro/repro_issue570.c \
tests/repro/repro_issue409.c \
tests/repro/repro_issue431.c \
tests/repro/repro_issue607.c \
tests/repro/repro_issue403.c \
tests/repro/repro_issue434.c \
tests/repro/repro_issue471.c \
tests/repro/repro_issue221.c \
tests/repro/repro_issue548.c \
tests/repro/repro_new_ts_class_field_arrow.c \
tests/repro/repro_new_py_tuple_unpack.c \
tests/repro/repro_new_cypher_limit_zero.c \
tests/repro/repro_issue363.c \
tests/repro/repro_issue581.c \
tests/repro/repro_issue787.c \
tests/repro/repro_issue842.c \
tests/repro/repro_issue964.c \
tests/repro/repro_invariant_calls.c \
tests/repro/repro_invariant_graph.c \
tests/repro/repro_invariant_breadth.c \
tests/repro/repro_invariant_enclosing_parity.c \
tests/repro/repro_invariant_lsp_rescue.c \
tests/repro/repro_invariant_discovery_fqn.c \
tests/repro/repro_grammar_core.c \
tests/repro/repro_grammar_scripting.c \
tests/repro/repro_grammar_functional.c \
tests/repro/repro_grammar_systems.c \
tests/repro/repro_grammar_web.c \
tests/repro/repro_grammar_config.c \
tests/repro/repro_grammar_build.c \
tests/repro/repro_grammar_shells.c \
tests/repro/repro_grammar_scientific.c \
tests/repro/repro_grammar_markup.c \
tests/repro/repro_grammar_misc.c \
tests/repro/repro_lsp_c_cpp.c \
tests/repro/repro_lsp_go_py.c \
tests/repro/repro_lsp_ts.c \
tests/repro/repro_ts_inherited_method.c \
tests/repro/repro_lsp_java_cs.c \
tests/repro/repro_lsp_kt_php_rust.c
ALL_TEST_SRCS =$(TEST_FOUNDATION_SRCS) $(TEST_EXTRACTION_SRCS) $(TEST_STORE_SRCS) $(TEST_CYPHER_SRCS) $(TEST_MCP_SRCS) $(TEST_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) | $(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
$(BUILD_DIR)/tre.o: $(TRE_SRC) | $(BUILD_DIR)
$(CC) $(TRE_CFLAGS) $(SANITIZE) -fno-sanitize=alignment -c -o $@ $<
$(BUILD_DIR)/tsan_tre.o: $(TRE_SRC) | $(BUILD_DIR)
$(CC) $(TRE_CFLAGS) $(TSAN_SANITIZE) -c -o $@ $<
$(BUILD_DIR)/prod_tre.o: $(TRE_SRC) | $(BUILD_DIR)
$(CC) $(TRE_CFLAGS) -O2 -c -o $@ $<
endif
# Vendored LZ4 (test build)
LZ4_OBJ_TEST = $(BUILD_DIR)/test_lz4.o $(BUILD_DIR)/test_lz4hc.o
LZ4_OBJ_TSAN = $(BUILD_DIR)/tsan_lz4.o $(BUILD_DIR)/tsan_lz4hc.o
$(BUILD_DIR)/test_lz4.o: $(CBM_DIR)/vendored/lz4/lz4.c | $(BUILD_DIR)
$(CC) -std=c11 -D_DEFAULT_SOURCE -g -O1 $(SANITIZE) -w -I$(CBM_DIR) -c -o $@ $<
$(BUILD_DIR)/test_lz4hc.o: $(CBM_DIR)/vendored/lz4/lz4hc.c | $(BUILD_DIR)
$(CC) -std=c11 -D_DEFAULT_SOURCE -g -O1 $(SANITIZE) -w -I$(CBM_DIR)/vendored/lz4 -c -o $@ $<
$(BUILD_DIR)/tsan_lz4.o: $(CBM_DIR)/vendored/lz4/lz4.c | $(BUILD_DIR)
$(CC) -std=c11 -D_DEFAULT_SOURCE -g -O1 $(TSAN_SANITIZE) -w -I$(CBM_DIR) -c -o $@ $<
$(BUILD_DIR)/tsan_lz4hc.o: $(CBM_DIR)/vendored/lz4/lz4hc.c | $(BUILD_DIR)
$(CC) -std=c11 -D_DEFAULT_SOURCE -g -O1 $(TSAN_SANITIZE) -w -I$(CBM_DIR)/vendored/lz4 -c -o $@ $<
# Vendored zstd (test build)
ZSTD_OBJ_TEST = $(BUILD_DIR)/test_zstd.o
ZSTD_OBJ_TSAN = $(BUILD_DIR)/tsan_zstd.o
$(BUILD_DIR)/test_zstd.o: $(CBM_DIR)/vendored/zstd/zstd.c | $(BUILD_DIR)
$(CC) -std=c11 -D_DEFAULT_SOURCE -g -O1 $(SANITIZE) -w -I$(CBM_DIR)/vendored/zstd -c -o $@ $<
$(BUILD_DIR)/tsan_zstd.o: $(CBM_DIR)/vendored/zstd/zstd.c | $(BUILD_DIR)
$(CC) -std=c11 -D_DEFAULT_SOURCE -g -O1 $(TSAN_SANITIZE) -w -I$(CBM_DIR)/vendored/zstd -c -o $@ $<
# nomic-embed-code pretrained vector blob
UNIXCODER_OBJ = $(BUILD_DIR)/unixcoder_blob.o
$(UNIXCODER_OBJ): $(UNIXCODER_BLOB_SRC) vendored/nomic/code_vectors.bin | $(BUILD_DIR)
$(CC) -c -o $@ $<
OBJS_VENDORED_TEST = $(MIMALLOC_OBJ_TEST) $(SQLITE3_OBJ_TEST) $(TRE_OBJ_TEST) $(GRAMMAR_OBJS_TEST) $(TS_RUNTIME_OBJ_TEST) $(LSP_OBJ_TEST) $(PP_OBJ_TEST) $(LZ4_OBJ_TEST) $(ZSTD_OBJ_TEST) $(UNIXCODER_OBJ)
OBJS_VENDORED_TSAN = $(MIMALLOC_OBJ_TSAN) $(SQLITE3_OBJ_TSAN) $(TRE_OBJ_TSAN) $(GRAMMAR_OBJS_TSAN) $(TS_RUNTIME_OBJ_TSAN) $(LSP_OBJ_TSAN) $(PP_OBJ_TSAN) $(LZ4_OBJ_TSAN) $(ZSTD_OBJ_TSAN) $(UNIXCODER_OBJ)
$(BUILD_DIR)/test-runner: $(ALL_TEST_SRCS) $(PROD_SRCS) $(EXTRACTION_SRCS) $(AC_LZ4_SRCS) $(ZSTD_SRCS) $(SQLITE_WRITER_SRC) $(OBJS_VENDORED_TEST) | $(BUILD_DIR)
$(CC) $(CFLAGS_TEST) -Itests -Itests/repro -o $@ \
$(ALL_TEST_SRCS) $(PROD_SRCS) \
$(EXTRACTION_SRCS) $(AC_LZ4_SRCS) $(ZSTD_SRCS) $(SQLITE_WRITER_SRC) \
$(OBJS_VENDORED_TEST) \
$(LDFLAGS_TEST)
test: $(BUILD_DIR)/test-runner
cd $(CURDIR) && $(BUILD_DIR)/test-runner
# 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 by design, non-gating) ──
# Mirrors test-runner's link line but uses repro_main.c (own main + counters)
# and TEST_REPRO_SRCS instead of ALL_TEST_SRCS. Exits non-zero while any bug is
# still reproduced (the expected state); bug-repro.yml surfaces it as a board.
$(BUILD_DIR)/test-repro-runner: $(TEST_REPRO_SRCS) $(PROD_SRCS) $(EXTRACTION_SRCS) $(AC_LZ4_SRCS) $(ZSTD_SRCS) $(SQLITE_WRITER_SRC) $(OBJS_VENDORED_TEST) | $(BUILD_DIR)
$(CC) $(CFLAGS_TEST) -Itests -o $@ \
$(TEST_REPRO_SRCS) $(PROD_SRCS) \
$(EXTRACTION_SRCS) $(AC_LZ4_SRCS) $(ZSTD_SRCS) $(SQLITE_WRITER_SRC) \
$(OBJS_VENDORED_TEST) \
$(LDFLAGS_TEST)
test-repro: $(BUILD_DIR)/test-repro-runner
cd $(CURDIR) && $(BUILD_DIR)/test-repro-runner
# ── TSan full test ───────────────────────────────────────────────
# 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.
#
# Deliberately EXCLUDED (documented, not forgotten):
# daemon_runtime — deadlocks under TSan: it forks after going
# multi-threaded and the child re-enters the runtime, a
# combination TSan's runtime does not support (hangs).
# daemon_ipc — a TEST-harness data race (a helper thread writes a
# main-thread stack slot without synchronization,
# test_daemon_ipc.c:2088); production IPC is not implicated.
# daemon_frontend — TEST-harness thread leaks (fixture threads left unjoined,
# test_daemon_frontend.c:698) plus a fork-under-TSan fixture
# failure. Test-quality work, tracked separately.
# Re-enabling these three needs test-harness synchronization + a TSan-fork
# strategy; it is out of scope for the current sanitizer-coverage pass.
TEST_TSAN_SUITES ?= mem slab_alloc parallel worker_pool watcher httpd pipeline \
diagnostics mcp mcp_mutation_guard subprocess daemon daemon_application
TSAN_OPTIONS ?= halt_on_error=1
# 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) | $(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 | $(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 ==="
# ── 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 ==="