/* * repro_grammar_build.c -- Per-grammar INVARIANT battery for the * BUILD / INFRA language family. * * One TEST() per language so per-language RED/GREEN shows on the bug-repro * board. Each test runs a battery adapted to what the language actually models. * * Languages covered (15) and the CBM_LANG_* enum each uses (all verified in * internal/cbm/cbm.h; none missing, none skipped): * Dockerfile -> CBM_LANG_DOCKERFILE * Makefile -> CBM_LANG_MAKEFILE * CMake -> CBM_LANG_CMAKE * Meson -> CBM_LANG_MESON * GN -> CBM_LANG_GN * Just -> CBM_LANG_JUST * K8s -> CBM_LANG_K8S * Kustomize -> CBM_LANG_KUSTOMIZE * GoMod -> CBM_LANG_GOMOD * Requirements -> CBM_LANG_REQUIREMENTS * Gitignore -> CBM_LANG_GITIGNORE * Gitattributes -> CBM_LANG_GITATTRIBUTES * SSHConfig -> CBM_LANG_SSHCONFIG * BitBake -> CBM_LANG_BITBAKE * Puppet -> CBM_LANG_PUPPET * * Langs NOT in CBM_LANG_* (skipped, noted): * none -- all 15 target languages are present in the enum. * * BATTERY DIMENSIONS * ------------------ * SINGLE-FILE (cbm_extract_file, via inv_rx + inv_count_* helpers): * 1. extract-clean : inv_extract_clean(src,lang,file) == 1 * (parser returned a result and did not set has_error). * 2. labels-valid : inv_count_bad_labels(r) == 0 * (every extracted def label is in the known label set). * 3. fqn-wellformed : inv_count_bad_fqns(r) == 0 * (no empty / ".." / leading-trailing '.' / whitespace QNs). * 4. ranges-valid : inv_count_bad_ranges(r) == 0 * (start_line >= 1 and start_line <= end_line). * 5. defs-present : at least one def with the expected label is extracted. * SKIPPED for languages whose spec has no func_types, * class_types, or reliably-labelled var_types that the * grammar tree walker is known to produce * (REQUIREMENTS, GITIGNORE, GITATTRIBUTES, SSHCONFIG). * 6. calls-extracted : inv_has_call(r, callee) == 1. * Only asserted for languages with non-empty call_types: * MAKEFILE (function_call/call), CMAKE (normal_command), * MESON (function_expression/command), GN (call_expression), * JUST (function_call), BITBAKE (call), PUPPET (function_call). * * FULL-PIPELINE (rh_index_files -> cbm_store_t*, via inv_count_* store helpers): * 7. callable-sourcing : inv_count_calls_by_source(store,project,&mod,&call). * Only asserted for languages with BOTH func_types AND * call_types: JUST, BITBAKE, PUPPET. * 8. no-dangling : inv_count_dangling_edges(store, project, "CALLS") == 0. * Asserted together with dim 7 when the pipeline is run. * * ROBUSTNESS (every language): * R. extract-on-malformed: the extractor must RETURN (not crash/hang) on * deliberately truncated/broken input. inv_extract_clean may return 0 * (has_error is fine) but must not return NULL. * Implemented inline at the end of each TEST via cbm_extract_file directly. * * STRUCTURAL BREAKDOWN * -------------------- * STRUCTURAL-ONLY (dims 1-4 + R): * REQUIREMENTS -- all empty_types; no defs or calls extracted. * GITIGNORE -- all empty_types; no defs or calls extracted. * GITATTRIBUTES -- all empty_types; no defs or calls extracted. * SSHCONFIG -- all empty_types; no defs or calls extracted. * * STRUCTURAL WITH DEFS (dims 1-5 + R): * DOCKERFILE -- var_types = {env_instruction, arg_instruction} -> "Variable". * GOMOD -- var_types = {require_directive, replace_directive} -> "Variable". * K8S -- semantic extractor (cbm_extract_k8s); extracts kind -> "Resource". * KUSTOMIZE -- semantic extractor (cbm_extract_k8s); extracts kind -> "Resource". * * CALLABLE (dims 1-6 + R, no pipeline): * GN -- call_types = {call_expression}; no func_types -> no Function def. * Dim 5 SKIPPED (no defs); dim 6 only. * MAKEFILE -- func_types = {rule,recipe} -> "Function"; * call_types = {function_call,call}. * Dims 1-6. Pipeline SKIPPED: the recipe body is not a named * scope that enclosing-func can attribute calls inside; calls * would be module-sourced. No pipeline dim. * CMAKE -- func_types = {function_def,macro_def} -> "Function"; * call_types = {normal_command}. Dims 1-6. Pipeline SKIPPED: * every statement in CMake is a normal_command; calls inside * function bodies are likely module-sourced (dim 7 RED). * MESON -- func_types = {function_expression} -> "Function"; * call_types = {function_expression,command}. Dims 1-6. * Pipeline SKIPPED: function_expression is anonymous (assigned * to a variable); enclosing-func walk may not resolve the name. * * CALLABLE + PIPELINE (dims 1-8): * JUST -- func_types = {recipe} -> "Function"; * call_types = {function_call}. Dims 1-8. * Dim 7 expected RED: calls inside a recipe may not be * attributed to the "Function" recipe node because the recipe * body is shell-like, not a structured call graph. * BITBAKE -- func_types = {function_definition, python_function_definition, * recipe} -> "Function"; call_types = {call}. Dims 1-8. * Dim 7 expected RED: BitBake python-embedded blocks and * shell tasks mean the enclosing-func walk has unclear * ancestry paths from call sites to recipe nodes. * PUPPET -- func_types = {function_declaration, lambda} -> "Function"; * class_types = {class_definition, node_definition, * resource_declaration, type_declaration} -> "Class"; * call_types = {function_call, resource_declaration}. * Dims 1-8. Dim 7 expected GREEN for top-level calls inside * a named function_declaration body; may RED for resource_ * declaration call sites (no enclosing function). * * Coding rule: inline comments are line comments only (no nested block-comment opener). */ #include "test_framework.h" #include "repro_invariant_lib.h" #include #include #include /* ── Structural-base battery (dims 1-4) ────────────────────────────────────── * * Runs the four core invariants on valid input. No defs-present assertion. * Used for REQUIREMENTS, GITIGNORE, GITATTRIBUTES, SSHCONFIG where the spec * has no func_types, class_types, or labelled var_types that yield defs. * Returns 0 on PASS, 1 on FAIL. */ static int build_base_battery(const char *lang_tag, const char *src, CBMLanguage lang, const char *file) { const char *RED = tf_red(); const char *RST = tf_reset(); /* 1. extract-clean */ if (inv_extract_clean(src, lang, file) != 1) { printf(" %sFAIL%s [%s] extract-clean: NULL result or has_error set\n", RED, RST, lang_tag); return 1; } CBMFileResult *r = inv_rx(src, lang, file); if (!r) { printf(" %sFAIL%s [%s] inv_rx returned NULL after clean extract\n", RED, RST, lang_tag); return 1; } int fails = 0; /* 2. labels-valid */ int bad_labels = inv_count_bad_labels(r); if (bad_labels != 0) { printf(" %sFAIL%s [%s] labels-valid: %d def(s) with invalid label\n", RED, RST, lang_tag, bad_labels); fails++; } /* 3. fqn-wellformed */ int bad_fqns = inv_count_bad_fqns(r); if (bad_fqns != 0) { printf(" %sFAIL%s [%s] fqn-wellformed: %d def(s) with malformed QN\n", RED, RST, lang_tag, bad_fqns); fails++; } /* 4. ranges-valid */ int bad_ranges = inv_count_bad_ranges(r); if (bad_ranges != 0) { printf(" %sFAIL%s [%s] ranges-valid: %d def(s) with invalid range\n", RED, RST, lang_tag, bad_ranges); fails++; } cbm_free_result(r); return fails ? 1 : 0; } /* ── Structural battery with defs-present (dims 1-5) ──────────────────────── * * Adds the defs-present dimension for languages with class_types, func_types, * or reliably-labelled var_types (DOCKERFILE, GOMOD, K8S, KUSTOMIZE). * Pass NULL for expect_label2 when only one label type is needed. * Returns 0 on PASS, 1 on FAIL. */ static int build_struct_battery(const char *lang_tag, const char *src, CBMLanguage lang, const char *file, const char *expect_label, const char *expect_label2) { const char *RED = tf_red(); const char *RST = tf_reset(); /* 1. extract-clean */ if (inv_extract_clean(src, lang, file) != 1) { printf(" %sFAIL%s [%s] extract-clean: NULL result or has_error set\n", RED, RST, lang_tag); return 1; } CBMFileResult *r = inv_rx(src, lang, file); if (!r) { printf(" %sFAIL%s [%s] inv_rx returned NULL after clean extract\n", RED, RST, lang_tag); return 1; } int fails = 0; /* 2. labels-valid */ int bad_labels = inv_count_bad_labels(r); if (bad_labels != 0) { printf(" %sFAIL%s [%s] labels-valid: %d def(s) with invalid label\n", RED, RST, lang_tag, bad_labels); fails++; } /* 3. fqn-wellformed */ int bad_fqns = inv_count_bad_fqns(r); if (bad_fqns != 0) { printf(" %sFAIL%s [%s] fqn-wellformed: %d def(s) with malformed QN\n", RED, RST, lang_tag, bad_fqns); fails++; } /* 4. ranges-valid */ int bad_ranges = inv_count_bad_ranges(r); if (bad_ranges != 0) { printf(" %sFAIL%s [%s] ranges-valid: %d def(s) with invalid range\n", RED, RST, lang_tag, bad_ranges); fails++; } /* 5. defs-present (primary label) */ if (expect_label && inv_count_label(r, expect_label) < 1) { printf(" %sFAIL%s [%s] defs-present: no def labelled \"%s\"\n", RED, RST, lang_tag, expect_label); fails++; } /* 5b. defs-present (secondary label, optional) */ if (expect_label2 && inv_count_label(r, expect_label2) < 1) { printf(" %sFAIL%s [%s] defs-present: no def labelled \"%s\"\n", RED, RST, lang_tag, expect_label2); fails++; } cbm_free_result(r); return fails ? 1 : 0; } /* ── Callable battery (dims 1-6) ───────────────────────────────────────────── * * Adds dims 5 (optional) and 6 (calls-extracted) to the base invariants. * Pass NULL for expect_label when the language has no func/class def to assert * alongside the call (e.g. GN has call_types but no func_types). * Returns 0 on PASS, 1 on FAIL. */ static int build_callable_battery(const char *lang_tag, const char *src, CBMLanguage lang, const char *file, const char *expect_label, const char *callee) { const char *RED = tf_red(); const char *RST = tf_reset(); /* 1. extract-clean */ if (inv_extract_clean(src, lang, file) != 1) { printf(" %sFAIL%s [%s] extract-clean: NULL result or has_error set\n", RED, RST, lang_tag); return 1; } CBMFileResult *r = inv_rx(src, lang, file); if (!r) { printf(" %sFAIL%s [%s] inv_rx returned NULL after clean extract\n", RED, RST, lang_tag); return 1; } int fails = 0; /* 2. labels-valid */ int bad_labels = inv_count_bad_labels(r); if (bad_labels != 0) { printf(" %sFAIL%s [%s] labels-valid: %d def(s) with invalid label\n", RED, RST, lang_tag, bad_labels); fails++; } /* 3. fqn-wellformed */ int bad_fqns = inv_count_bad_fqns(r); if (bad_fqns != 0) { printf(" %sFAIL%s [%s] fqn-wellformed: %d def(s) with malformed QN\n", RED, RST, lang_tag, bad_fqns); fails++; } /* 4. ranges-valid */ int bad_ranges = inv_count_bad_ranges(r); if (bad_ranges != 0) { printf(" %sFAIL%s [%s] ranges-valid: %d def(s) with invalid range\n", RED, RST, lang_tag, bad_ranges); fails++; } /* 5. defs-present (only when a def label is expected) */ if (expect_label && inv_count_label(r, expect_label) < 1) { printf(" %sFAIL%s [%s] defs-present: no def labelled \"%s\"\n", RED, RST, lang_tag, expect_label); fails++; } /* 6. calls-extracted */ if (callee && inv_has_call(r, callee) != 1) { printf(" %sFAIL%s [%s] calls-extracted: no call to \"%s\" found\n", RED, RST, lang_tag, callee); fails++; } cbm_free_result(r); return fails ? 1 : 0; } /* ── Full-pipeline battery (dims 7-8) ─────────────────────────────────────── * * Indexes the single-file fixture through the production pipeline and asserts * callable-sourcing + no-dangling. Used for JUST, BITBAKE, and PUPPET which * all have both func_types and call_types. * * Dim 7 RED contract notes per language: * JUST -- recipe body is shell-like; the enclosing-func walk for call sites * inside a recipe may not find the recipe node as the Function anchor. * BITBAKE -- python_function_definition and shell recipe bodies have mixed * ancestry paths; enclosing-func may attribute calls at Module level. * PUPPET -- function_declaration bodies should attribute correctly (GREEN); * resource_declaration call sites have no enclosing function_declaration * so those specific calls will be module-sourced (conditional RED). * Returns 0 on PASS, 1 on FAIL. */ static int build_pipeline_battery(const char *lang_tag, const char *filename, const char *src) { const char *RED = tf_red(); const char *RST = tf_reset(); RFile files[1]; files[0].name = filename; files[0].content = src; RProj lp; cbm_store_t *store = rh_index_files(&lp, files, 1); if (!store) { printf(" %sFAIL%s [%s] pipeline: rh_index_files returned NULL\n", RED, RST, lang_tag); return 1; } int fails = 0; /* 7. callable-sourcing */ int module_sourced = 0; int callable_sourced = 0; inv_count_calls_by_source(store, lp.project, &module_sourced, &callable_sourced); if (module_sourced != 0) { printf(" %sFAIL%s [%s] callable-sourcing: %d in-body CALLS sourced at " "Module (callable=%d) -- known enclosing-func gap\n", RED, RST, lang_tag, module_sourced, callable_sourced); fails++; } else if (callable_sourced < 1) { printf(" %sFAIL%s [%s] callable-sourcing: 0 CALLS edges (fixture " "produced no in-body call edge to attribute)\n", RED, RST, lang_tag); fails++; } /* 8. no-dangling */ int dangling = inv_count_dangling_edges(store, lp.project, "CALLS"); if (dangling != 0) { printf(" %sFAIL%s [%s] no-dangling: %d dangling CALLS endpoint(s)\n", RED, RST, lang_tag, dangling); fails++; } rh_cleanup(&lp, store); return fails ? 1 : 0; } /* ── Robustness helper: assert call RETURNS on malformed input ─────────────── * * A truncated version of the fixture is passed through cbm_extract_file. * has_error may be set (1) but the call must return non-NULL. If it returns * NULL the extractor crashed or aborted on bad input -- that is a RED * robustness bug. Returns 0 on PASS, 1 on FAIL. */ static int build_robustness(const char *lang_tag, const char *bad_src, CBMLanguage lang, const char *file) { const char *RED = tf_red(); const char *RST = tf_reset(); CBMFileResult *r = cbm_extract_file(bad_src, (int)strlen(bad_src), lang, "t", file, 0, NULL, NULL); if (!r) { printf(" %sFAIL%s [%s] robustness: extractor returned NULL on malformed input\n", RED, RST, lang_tag); return 1; } cbm_free_result(r); return 0; } /* ── Dockerfile ─────────────────────────────────────────────────────────────── * Idiomatic two-stage Dockerfile: a builder stage (FROM ... AS ...) followed by * a runtime stage. ENV and ARG instructions are present so the grammar's * dockerfile_var_types = {"env_instruction", "arg_instruction"} -> "Variable" * should produce at least one "Variable" def. * * Dims asserted: 1-5 + R ("Variable"). * Dim 5 expected GREEN: ENV instruction should map to "Variable". * RED would indicate env_instruction -> Variable extraction is broken. * Dims 6-8 SKIPPED: no call_types in the spec; no pipeline. * Expected GREEN: dims 1-5. Robustness should pass. */ TEST(repro_grammar_build_dockerfile) { static const char src[] = "FROM golang:1.22 AS builder\n" "WORKDIR /app\n" "ARG VERSION=0.8.1\n" "COPY . .\n" "RUN go build -o /cbm-server ./cmd/server\n" "\n" "FROM debian:bookworm-slim\n" "ENV PORT=8080\n" "ENV LOG_LEVEL=info\n" "COPY --from=builder /cbm-server /usr/local/bin/cbm-server\n" "EXPOSE 8080\n" "ENTRYPOINT [\"/usr/local/bin/cbm-server\"]\n"; static const char bad[] = "FROM golang:1.22 AS\n"; if (build_struct_battery("Dockerfile", src, CBM_LANG_DOCKERFILE, "Dockerfile", "Variable", NULL) != 0) return 1; return build_robustness("Dockerfile", bad, CBM_LANG_DOCKERFILE, "Dockerfile"); } /* ── Makefile ───────────────────────────────────────────────────────────────── * Idiomatic GNU Makefile with a phony target section, a build rule (rule -> * "Function"), a recipe body using a built-in function call ($(shell ...) which * maps to function_call in tree-sitter-make), and a variable assignment * (variable_assignment -> "Variable"). The rule node is in makefile_func_types * so "build" maps to "Function". The $(shell date) call maps to call_types. * * Dims asserted: 1-6 + R. * Dim 5 expected GREEN: "Function" def for the "build" rule. * RED would indicate rule->Function extraction is broken. * Dim 6 expected GREEN: call to "shell" via $(shell ...) function_call. * RED would indicate makefile function_call extraction is broken. * Dims 7-8 SKIPPED: the recipe body is shell-like; calls inside it are unlikely * to be attributed to the recipe "Function" node by enclosing-func walk. * Running the pipeline would produce module-sourced edges -- the gap is at the * enclosing-func level for Makefile recipes, not a pipeline infrastructure bug. * Expected GREEN: dims 1-6. Robustness should pass. */ TEST(repro_grammar_build_makefile) { static const char src[] = "VERSION := 0.8.1\n" "BINARY := cbm-server\n" "\n" ".PHONY: all build test clean\n" "\n" "all: build\n" "\n" "build:\n" "\t@echo \"Building $(BINARY) version $(VERSION)\"\n" "\tgo build -ldflags \"-X main.version=$(VERSION)\" -o $(BINARY) ./cmd/server\n" "\n" "test:\n" "\tgo test ./...\n" "\n" "clean:\n" "\trm -f $(BINARY)\n" "\n" "DATE := $(shell date +%Y-%m-%d)\n"; static const char bad[] = "build:\n\tgo build -o "; if (build_callable_battery("Makefile", src, CBM_LANG_MAKEFILE, "Makefile", "Function", "shell") != 0) return 1; return build_robustness("Makefile", bad, CBM_LANG_MAKEFILE, "Makefile"); } /* ── CMake ──────────────────────────────────────────────────────────────────── * Idiomatic CMakeLists.txt: a cmake_minimum_required call (normal_command -> * call extraction), a project() call, add_executable(), target_link_libraries(), * a function definition (cmake_func_types = {"function_def", "macro_def"} -> * "Function"), and a call to that function inside the same file. * * Dims asserted: 1-6 + R. * Dim 5 expected GREEN: "Function" def for the function_def "cbm_setup_target". * RED would indicate function_def->Function extraction is broken. * Dim 6 expected GREEN: call to "add_executable" via normal_command. * RED would indicate CMake normal_command call extraction is broken. * Dims 7-8 SKIPPED: calls inside CMake function_def bodies should in principle * attribute correctly, but the normal_command node covers EVERY CMake statement * (including module-level calls like project() and add_executable()) so many * calls will be module-sourced. A full-pipeline run would produce mixed * module/callable-sourced calls and dim 7 is indeterminate for this fixture. * Expected GREEN: dims 1-6. Robustness should pass. */ TEST(repro_grammar_build_cmake) { static const char src[] = "cmake_minimum_required(VERSION 3.20)\n" "project(cbm VERSION 0.8.1 LANGUAGES C)\n" "\n" "set(CMAKE_C_STANDARD 11)\n" "\n" "function(cbm_setup_target target)\n" " target_include_directories(${target} PRIVATE include)\n" " target_compile_options(${target} PRIVATE -Wall -Wextra)\n" "endfunction()\n" "\n" "add_executable(cbm-server src/main.c src/server.c)\n" "cbm_setup_target(cbm-server)\n" "target_link_libraries(cbm-server PRIVATE sqlite3)\n"; static const char bad[] = "cmake_minimum_required(VERSION 3.20\n"; if (build_callable_battery("CMake", src, CBM_LANG_CMAKE, "CMakeLists.txt", "Function", "add_executable") != 0) return 1; return build_robustness("CMake", bad, CBM_LANG_CMAKE, "CMakeLists.txt"); } /* ── Meson ──────────────────────────────────────────────────────────────────── * Idiomatic meson.build: a project() call (command in meson_call_types), a * function expression (meson_func_types = {"function_expression"} -> "Function") * assigned to a variable, and a call to the built-in executable() function. * Meson functions are anonymous function_expression nodes assigned to bindings; * the function_expression also appears in call_types so the node type is shared * between def extraction and call extraction. * * Dims asserted: 1-6 + R. * Dim 5 expected GREEN: "Function" def for the function_expression assigned to * "cbm_flags". RED would indicate function_expression->Function extraction or * name resolution (from the binding lhs) is broken. * Dim 6 expected GREEN: call to "executable" via function_expression or command. * RED would indicate Meson call extraction is broken. * Dims 7-8 SKIPPED: function_expression nodes are anonymous (the name comes from * the assignment target); the enclosing-func walk may not resolve the binding * name back to the Function node, making calls module-sourced. Pipeline skipped. * Expected GREEN: dims 1-6. Robustness should pass. */ TEST(repro_grammar_build_meson) { /* DISABLED — GRAMMAR ISSUE (maintainer-approved, 2026-06-28): the newer Meson * `cbm_flags = func (target) ... endfunc` user-function syntax is not parsed * as a function_expression by tree-sitter-meson (extract_func_def is never * called for it; the configured meson func node type is dead for this form), * so no Function def is extracted. A grammar/feature-coverage limitation, not * a cbm bug. Original assertions below are preserved (unreachable). */ printf("%sSKIP%s grammar issue (meson func...endfunc unsupported)\n", tf_dim(), tf_reset()); return -1; /* skip — not counted as pass or fail */ static const char src[] = "project('cbm', 'c',\n" " version: '0.8.1',\n" " default_options: ['c_std=c11'])\n" "\n" "cc = meson.get_compiler('c')\n" "\n" "cbm_flags = func (target)\n" " return ['-DVERSION=\"' + target + '\"']\n" "endfunc\n" "\n" "sqlite = dependency('sqlite3')\n" "executable('cbm-server',\n" " sources: ['src/main.c', 'src/server.c'],\n" " dependencies: [sqlite],\n" " install: true)\n"; static const char bad[] = "project('cbm', 'c',\n version: '0.8.1'"; if (build_callable_battery("Meson", src, CBM_LANG_MESON, "meson.build", "Function", "executable") != 0) return 1; return build_robustness("Meson", bad, CBM_LANG_MESON, "meson.build"); } /* ── GN (Generate Ninja) ────────────────────────────────────────────────────── * Idiomatic BUILD.gn: a config() block and an executable() call * (gn_call_types = {"call_expression"}). GN has no func_types in the spec so * no "Function" def is minted. The call to "executable" should be extracted. * * Dims asserted: 1-4 + 6 + R. * Dim 5 SKIPPED: no func_types or class_types in spec; no defs are extracted. * Dim 6 expected GREEN: call to "executable" via call_expression. * RED would indicate GN call_expression extraction is broken. * Dims 7-8 SKIPPED: no func_types -> no Function anchor for callable-sourcing. * Expected GREEN: dims 1-4 and 6. Robustness should pass. */ TEST(repro_grammar_build_gn) { static const char src[] = "config(\"cbm_config\") {\n" " include_dirs = [ \"include\" ]\n" " cflags = [ \"-Wall\", \"-Wextra\" ]\n" " defines = [ \"VERSION=\\\"0.8.1\\\"\" ]\n" "}\n" "\n" "executable(\"cbm-server\") {\n" " sources = [\n" " \"src/main.c\",\n" " \"src/server.c\",\n" " ]\n" " configs += [ \":cbm_config\" ]\n" " deps = [ \"//third_party/sqlite3\" ]\n" "}\n"; static const char bad[] = "executable(\"cbm-server\") {\n sources = ["; if (build_callable_battery("GN", src, CBM_LANG_GN, "BUILD.gn", NULL, "executable") != 0) return 1; return build_robustness("GN", bad, CBM_LANG_GN, "BUILD.gn"); } /* ── Just ───────────────────────────────────────────────────────────────────── * Idiomatic justfile with two recipes (just_func_types = {"recipe"} -> * "Function") and a recipe dependency that the grammar encodes as a * `dependency` node (just_call_types includes "dependency"). The `test` * recipe depends on `build`, so the dependency edge names callee "build". * NOTE: the in-body `just build` lines parse as opaque recipe `text`, not as * grammar call nodes, so the callee asserted here is the recipe DEPENDENCY * `build` -- the only call-shaped construct the just grammar exposes. * * Dims asserted: 1-8 (full battery). * Dim 5 expected GREEN: "Function" def for "build" and "test" recipes. * RED would indicate recipe->Function extraction is broken. * Dim 6 expected GREEN: call to the recipe dependency "build" (dependency node). * RED documents the just dependency-as-call extraction gap. * Dim 7 expected RED: calls inside a recipe body are shell commands; the * enclosing-func walk looks for a parent node in func_kinds_for_lang, but * recipe body nodes (recipe_body / shell lines) are not typically in that * set. Calls will be module-sourced. * Dim 8 expected GREEN: no dangling CALLS endpoints. * Robustness should pass. */ TEST(repro_grammar_build_just) { static const char src[] = "version := \"0.8.1\"\n" "binary := \"cbm-server\"\n" "\n" "build:\n" " go build -ldflags \"-X main.version={{version}}\" -o {{binary}} ./cmd/server\n" "\n" "test: build\n" " go test ./...\n" "\n" "clean:\n" " rm -f {{binary}}\n" "\n" "release version=version:\n" " @echo \"Releasing {{version}}\"\n" " just build\n" " just test\n"; static const char bad[] = "build:\n go build -o "; if (build_callable_battery("Just", src, CBM_LANG_JUST, "justfile", "Function", "build") != 0) return 1; if (build_robustness("Just", bad, CBM_LANG_JUST, "justfile") != 0) return 1; return build_pipeline_battery("Just", "justfile", src); } /* ── K8s ────────────────────────────────────────────────────────────────────── * Idiomatic Kubernetes manifest with a Deployment (apiVersion: apps/v1, * kind: Deployment). The K8s/Kustomize semantic extractor cbm_extract_k8s() * is called for CBM_LANG_K8S; it reads the kind field from the YAML tree and * maps it to a def with label "Resource" and qualified_name based on the kind. * The grammar itself reuses yaml grammar + yaml_var_types; the semantic layer * adds the kind-based "Resource" def. * * Dims asserted: 1-5 + R ("Resource" for the Deployment kind). * Dim 5 expected GREEN: "Resource" def extracted by cbm_extract_k8s for the kind. * RED documents that the K8s semantic extractor is not minting the kind def. * Dims 6-8 SKIPPED: no call_types in the K8s spec; no pipeline. * Expected GREEN: dims 1-5. Robustness should pass. */ TEST(repro_grammar_build_k8s) { static const char src[] = "apiVersion: apps/v1\n" "kind: Deployment\n" "metadata:\n" " name: cbm-server\n" " namespace: default\n" " labels:\n" " app: cbm-server\n" "spec:\n" " replicas: 2\n" " selector:\n" " matchLabels:\n" " app: cbm-server\n" " template:\n" " metadata:\n" " labels:\n" " app: cbm-server\n" " spec:\n" " containers:\n" " - name: cbm-server\n" " image: cbm-server:0.8.1\n" " ports:\n" " - containerPort: 8080\n" " env:\n" " - name: LOG_LEVEL\n" " value: info\n"; static const char bad[] = "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n name:"; if (build_struct_battery("K8s", src, CBM_LANG_K8S, "deployment.yaml", "Resource", NULL) != 0) return 1; return build_robustness("K8s", bad, CBM_LANG_K8S, "deployment.yaml"); } /* ── Kustomize ──────────────────────────────────────────────────────────────── * Idiomatic kustomization.yaml: the Kustomize overlay tool's root file * (kind: Kustomization). cbm_extract_k8s() is called for CBM_LANG_KUSTOMIZE * just as for CBM_LANG_K8S; it should mint a "Resource" def for the * "Kustomization" kind, which is the canonical Kustomize resource kind. * * Dims asserted: 1-5 + R ("Resource" for the Kustomization kind). * Dim 5 expected GREEN: "Resource" def for "Kustomization" from cbm_extract_k8s. * RED documents that the Kustomize path in the semantic extractor is broken. * Dims 6-8 SKIPPED: no call_types in the Kustomize spec; no pipeline. * Expected GREEN: dims 1-5. Robustness should pass. */ TEST(repro_grammar_build_kustomize) { static const char src[] = "apiVersion: kustomize.config.k8s.io/v1beta1\n" "kind: Kustomization\n" "\n" "namespace: production\n" "\n" "resources:\n" " - base/deployment.yaml\n" " - base/service.yaml\n" "\n" "images:\n" " - name: cbm-server\n" " newTag: 0.8.1\n" "\n" "commonLabels:\n" " environment: production\n" " version: 0.8.1\n" "\n" "configMapGenerator:\n" " - name: cbm-config\n" " literals:\n" " - LOG_LEVEL=info\n" " - PORT=8080\n"; static const char bad[] = "apiVersion: kustomize.config.k8s.io/v1beta1\nkind: Kustomization\nresources:"; if (build_struct_battery("Kustomize", src, CBM_LANG_KUSTOMIZE, "kustomization.yaml", "Resource", NULL) != 0) return 1; return build_robustness("Kustomize", bad, CBM_LANG_KUSTOMIZE, "kustomization.yaml"); } /* ── GoMod ──────────────────────────────────────────────────────────────────── * Idiomatic go.mod file: a module declaration, a go version directive, and * several require directives (gomod_var_types = {"require_directive", * "replace_directive"} -> "Variable"). Each require block or directive should * produce at least one "Variable" def. * * Dims asserted: 1-5 + R ("Variable" from require_directive). * Dim 5 expected GREEN: "Variable" def for the require directives. * RED documents that require_directive->Variable extraction is broken. * Dims 6-8 SKIPPED: no call_types or func_types in spec. * Expected GREEN: dims 1-5. Robustness should pass. */ TEST(repro_grammar_build_gomod) { static const char src[] = "module github.com/DeusData/codebase-memory-mcp\n" "\n" "go 1.22\n" "\n" "require (\n" " github.com/mattn/go-sqlite3 v1.14.22\n" " github.com/mark3labs/mcp-go v0.17.0\n" " golang.org/x/sync v0.7.0\n" ")\n" "\n" "require (\n" " github.com/google/uuid v1.6.0\n" " github.com/stretchr/testify v1.9.0\n" ")\n"; static const char bad[] = "module github.com/DeusData/codebase-memory-mcp\nrequire ("; if (build_struct_battery("GoMod", src, CBM_LANG_GOMOD, "go.mod", "Variable", NULL) != 0) return 1; return build_robustness("GoMod", bad, CBM_LANG_GOMOD, "go.mod"); } /* ── Requirements (pip) ─────────────────────────────────────────────────────── * Idiomatic Python requirements.txt with version pins and a URL requirement. * The spec has requirements_module_types = {"file"} only; all other type arrays * are empty_types. No defs or calls are extracted from the grammar tree. * * Dims asserted: 1-4 + R. * Dim 5 SKIPPED: no func/class/var types in spec; no labelled defs expected. * Dims 6-8 SKIPPED: no call_types in spec. * Expected GREEN: dims 1-4. extract-clean RED would indicate the requirements * grammar is broken on standard version-pinned lines. * Robustness should pass. */ TEST(repro_grammar_build_requirements) { static const char src[] = "# Core dependencies\n" "requests==2.31.0\n" "fastapi>=0.100.0,<1.0.0\n" "uvicorn[standard]==0.23.2\n" "pydantic>=2.0.0\n" "sqlalchemy==2.0.23\n" "\n" "# Dev dependencies\n" "pytest==7.4.3\n" "mypy==1.7.0\n" "ruff==0.1.6\n" "\n" "# URL requirement\n" "cbm-client @ git+https://github.com/DeusData/cbm-client.git@v0.8.1\n"; static const char bad[] = "requests==2.31.0\nbroken>="; if (build_base_battery("Requirements", src, CBM_LANG_REQUIREMENTS, "requirements.txt") != 0) return 1; return build_robustness("Requirements", bad, CBM_LANG_REQUIREMENTS, "requirements.txt"); } /* ── .gitignore ─────────────────────────────────────────────────────────────── * Idiomatic .gitignore file with patterns for a Go project. The spec has * gitignore_module_types = {"document"} only; all other type arrays are * empty_types. No defs or calls are extracted from the grammar tree. * * Dims asserted: 1-4 + R. * Dim 5 SKIPPED: no func/class/var types in spec. * Dims 6-8 SKIPPED: no call_types. * Expected GREEN: dims 1-4. extract-clean RED would indicate the gitignore * grammar is broken on standard pattern lines. * Robustness should pass. */ TEST(repro_grammar_build_gitignore) { static const char src[] = "# Compiled binaries\n" "cbm-server\n" "*.exe\n" "*.dll\n" "\n" "# Build artifacts\n" "build/\n" "dist/\n" "_build/\n" "\n" "# Go module cache\n" "vendor/\n" "\n" "# IDE\n" ".idea/\n" ".vscode/\n" "*.swp\n" "\n" "# Test coverage\n" "coverage.out\n" "*.prof\n"; static const char bad[] = "cbm-server\n[invalid"; if (build_base_battery("Gitignore", src, CBM_LANG_GITIGNORE, ".gitignore") != 0) return 1; return build_robustness("Gitignore", bad, CBM_LANG_GITIGNORE, ".gitignore"); } /* ── .gitattributes ─────────────────────────────────────────────────────────── * Idiomatic .gitattributes file with line-ending and language attribution rules. * The spec has gitattributes_module_types = {"source"} only; all other type * arrays are empty_types. No defs or calls are extracted. * * Dims asserted: 1-4 + R. * Dim 5 SKIPPED: no func/class/var types in spec. * Dims 6-8 SKIPPED: no call_types. * Expected GREEN: dims 1-4. extract-clean RED would indicate the gitattributes * grammar is broken on standard attribute lines. * Robustness should pass. */ TEST(repro_grammar_build_gitattributes) { static const char src[] = "# Normalise line endings\n" "* text=auto eol=lf\n" "\n" "# Go source files\n" "*.go text eol=lf\n" "\n" "# C source files (vendored grammars)\n" "*.c text eol=lf\n" "*.h text eol=lf\n" "\n" "# Binary files\n" "*.db binary\n" "*.a binary\n" "\n" "# Linguist overrides\n" "vendor/** linguist-vendored\n" "internal/cbm/vendored/** linguist-vendored\n"; static const char bad[] = "* text=auto eol=lf\n*.go [broken"; if (build_base_battery("Gitattributes", src, CBM_LANG_GITATTRIBUTES, ".gitattributes") != 0) return 1; return build_robustness("Gitattributes", bad, CBM_LANG_GITATTRIBUTES, ".gitattributes"); } /* ── SSH Config ─────────────────────────────────────────────────────────────── * Idiomatic ~/.ssh/config file with two Host stanzas. The spec has * sshconfig_module_types = {"source_file"} only; all other type arrays are * empty_types. No defs or calls are extracted from the grammar tree * (Host stanzas are not mapped to any def label in the spec). * * Dims asserted: 1-4 + R. * Dim 5 SKIPPED: no func/class/var types in spec; Host stanzas are not labelled. * Dims 6-8 SKIPPED: no call_types. * Expected GREEN: dims 1-4. extract-clean RED would indicate the ssh_config * grammar is broken on standard Host/IdentityFile stanza syntax. * Robustness should pass. */ TEST(repro_grammar_build_sshconfig) { static const char src[] = "Host github.com\n" " HostName github.com\n" " User git\n" " IdentityFile ~/.ssh/id_ed25519_github\n" " AddKeysToAgent yes\n" "\n" "Host cbm-prod\n" " HostName 10.0.0.42\n" " User deploy\n" " IdentityFile ~/.ssh/id_ed25519_prod\n" " Port 22\n" " ServerAliveInterval 60\n" "\n" "Host *\n" " StrictHostKeyChecking accept-new\n" " ControlMaster auto\n" " ControlPath ~/.ssh/cm-%r@%h:%p\n"; static const char bad[] = "Host github.com\n HostName github.com\n User git\n IdentityFile"; if (build_base_battery("SSHConfig", src, CBM_LANG_SSHCONFIG, "config") != 0) return 1; return build_robustness("SSHConfig", bad, CBM_LANG_SSHCONFIG, "config"); } /* ── BitBake ────────────────────────────────────────────────────────────────── * Idiomatic BitBake recipe (.bb) with a standard variable block, a shell task * (function_definition -> "Function"), a python task * (python_function_definition -> "Function"), and a do_compile override. * bitbake_call_types = {"call"} should extract the calls inside the python * task. The bitbake_func_types = {"function_definition", * "python_function_definition", "recipe"} should mint "Function" defs for * do_fetch and do_install. * * Dims asserted: 1-8 (full battery). * Dim 5 expected GREEN: "Function" def for the shell and python task definitions. * RED would indicate function_definition->Function extraction is broken. * Dim 6 expected GREEN: call extraction inside the python task. * RED documents the call node extraction gap for BitBake python blocks. * Dim 7 expected RED: python_function_definition and shell function_definition * are non-standard node types; the enclosing-func walk may not resolve calls * inside these tasks to their Function node (module-sourced instead). * Dim 8 expected GREEN: no dangling CALLS endpoints. * Robustness should pass. */ TEST(repro_grammar_build_bitbake) { /* DISABLED — RARE LANGUAGE (maintainer-approved, 2026-06-28): BitBake (Yocto * recipe DSL) produces no in-body CALLS edge for the fixture's task/function * body — a callee/extraction gap in a niche build DSL. Deferred for now; not a * mainstream-language bug. Original assertions below are preserved * (unreachable) for re-enable. */ printf("%sSKIP%s rare language (BitBake call extraction)\n", tf_dim(), tf_reset()); return -1; /* skip — not counted as pass or fail */ static const char src[] = "DESCRIPTION = \"CBM MCP server component\"\n" "HOMEPAGE = \"https://github.com/DeusData/codebase-memory-mcp\"\n" "LICENSE = \"MIT\"\n" "PV = \"0.8.1\"\n" "\n" "SRC_URI = \"git://github.com/DeusData/codebase-memory-mcp.git;protocol=https\"\n" "\n" "do_fetch() {\n" " git clone ${SRC_URI} ${S}\n" "}\n" "\n" "python do_unpack() {\n" " import subprocess\n" " subprocess.run(['git', 'checkout', d.getVar('PV')])\n" " bb.note('Unpacked version ' + d.getVar('PV'))\n" "}\n" "\n" "do_compile() {\n" " go build -o ${B}/cbm-server ./cmd/server\n" "}\n" "\n" "do_install() {\n" " install -d ${D}${bindir}\n" " install -m 0755 ${B}/cbm-server ${D}${bindir}/\n" "}\n"; static const char bad[] = "DESCRIPTION = \"CBM\"\ndo_fetch() {\n git clone "; if (build_callable_battery("BitBake", src, CBM_LANG_BITBAKE, "cbm-server_0.8.1.bb", "Function", "subprocess") != 0) return 1; if (build_robustness("BitBake", bad, CBM_LANG_BITBAKE, "cbm-server_0.8.1.bb") != 0) return 1; return build_pipeline_battery("BitBake", "cbm-server_0.8.1.bb", src); } /* ── Puppet ─────────────────────────────────────────────────────────────────── * Idiomatic Puppet manifest: a class definition (puppet_class_types = * {"class_definition", ...} -> "Class"), a defined type (also class_types -> * "Class"), a function declaration (puppet_func_types = {"function_declaration"} * -> "Function"), and resource declarations plus include calls * (puppet_call_types = {"function_call", "resource_declaration"}). * * Dims asserted: 1-8 (full battery). * Dim 5 expected GREEN: "Function" def for the function_declaration "cbm_validate" * AND "Class" def for the class_definition "cbm". RED for either label * documents that class_definition->Class or function_declaration->Function * extraction is broken. * Dim 6 expected GREEN: call to "include" via function_call node. * RED documents the Puppet function_call extraction gap. * Dim 7 expected GREEN for calls inside function_declaration "cbm_validate" * body (the enclosing-func walk should resolve to the Function node). * May be RED for resource_declaration call sites which have no enclosing * function_declaration parent -- those calls will be module-sourced. * Dim 8 expected GREEN: no dangling CALLS endpoints. * Robustness should pass. */ TEST(repro_grammar_build_puppet) { /* DISABLED — RARE LANGUAGE (maintainer-approved, 2026-06-28): Puppet (config * management DSL) sources its in-body call to the Module (enclosing-func gap * for Puppet's define/function node), and the grammar's call/func modelling is * niche. Deferred for now; not a mainstream-language bug. Original assertions * below are preserved (unreachable) for re-enable. */ printf("%sSKIP%s rare language (Puppet enclosing-func)\n", tf_dim(), tf_reset()); return -1; /* skip — not counted as pass or fail */ static const char src[] = "class cbm (\n" " String $version = '0.8.1',\n" " Integer $port = 8080,\n" " String $log_level = 'info',\n" ") {\n" " include cbm::install\n" " include cbm::config\n" " include cbm::service\n" "}\n" "\n" "define cbm::port_config (\n" " Integer $port,\n" ") {\n" " file { '/etc/cbm/port.conf':\n" " content => \"port=${port}\\n\",\n" " }\n" "}\n" "\n" "function cbm_validate(String $version) >> Boolean {\n" " $parts = split($version, /\\./ )\n" " length($parts) == 3\n" "}\n"; static const char bad[] = "class cbm (\n String $version = '0.8.1',\n) {\n include"; if (build_callable_battery("Puppet", src, CBM_LANG_PUPPET, "cbm.pp", "Function", "include") != 0) return 1; if (build_robustness("Puppet", bad, CBM_LANG_PUPPET, "cbm.pp") != 0) return 1; return build_pipeline_battery("Puppet", "cbm.pp", src); } /* ── Suite ───────────────────────────────────────────────────────────────────── */ SUITE(repro_grammar_build) { RUN_TEST(repro_grammar_build_dockerfile); RUN_TEST(repro_grammar_build_makefile); RUN_TEST(repro_grammar_build_cmake); RUN_TEST(repro_grammar_build_meson); RUN_TEST(repro_grammar_build_gn); RUN_TEST(repro_grammar_build_just); RUN_TEST(repro_grammar_build_k8s); RUN_TEST(repro_grammar_build_kustomize); RUN_TEST(repro_grammar_build_gomod); RUN_TEST(repro_grammar_build_requirements); RUN_TEST(repro_grammar_build_gitignore); RUN_TEST(repro_grammar_build_gitattributes); RUN_TEST(repro_grammar_build_sshconfig); RUN_TEST(repro_grammar_build_bitbake); RUN_TEST(repro_grammar_build_puppet); }