# How the codebase-memory-mcp Configuration System Handles .cbmignore and File Filtering

> Discover how the codebase-memory-mcp configuration system filters files using a five-layer ignore pipeline including Git ignore patterns and custom .cbmignore rules for precise control.

- Repository: [Martin Vogel/codebase-memory-mcp](https://github.com/DeusData/codebase-memory-mcp)
- Tags: how-to-guide
- Published: 2026-07-16

---

**The codebase-memory-mcp configuration system uses a deterministic five-layer ignore pipeline that processes files from built-in exclusions through Git ignore patterns to project-specific .cbmignore rules, where the last matching pattern wins within each layer.**

The discovery engine in codebase-memory-mcp implements a sophisticated file filtering mechanism that determines which paths enter the index by evaluating ignore rules in strict hierarchical order. This configuration system combines hard-coded exclusions with dynamic pattern matching to give developers granular control while maintaining predictable indexing behavior across large repositories.

## The Layered Ignore Pipeline in codebase-memory-mcp

The system processes potential paths through five distinct layers, stopping immediately when any layer rejects a path. Within each layer, the **last matching rule wins**, allowing subsequent patterns to override earlier ones at the same precedence level.

### Layer 1: Built-In Skip List

The foundation consists of hard-coded directory names that cannot be overridden by any configuration file. According to [`src/discover/discover.c`](https://github.com/DeusData/codebase-memory-mcp/blob/main/src/discover/discover.c) lines 514–516, these include `.git`, `node_modules`, `dist`, and other common build artifacts. These exclusions are enforced via the `is_safety_core_dir` check before any pattern matching occurs, making them absolute barriers in the pipeline.

### Layers 2–3: Git ignore Integration

The next layers process standard Git ignore patterns. First, the repository-level `.gitignore` combined with `.git/info/exclude` is evaluated. During the directory walk, the engine discovers and applies **nested `.gitignore` files** relative to their containing directories. These follow standard gitignore syntax and precedence, with patterns evaluated in standard Git order.

### Layer 4: Project-Specific .cbmignore

The fourth layer introduces the project-specific `.cbmignore` file, loaded **only from the repository root** (`<repo>/.cbmignore`). As specified in [`docs/cbmignore.md`](https://github.com/DeusData/codebase-memory-mcp/blob/main/docs/cbmignore.md) line 5, nested `.cbmignore` files are explicitly ignored. This file supports full gitignore syntax including wildcards, `**` globstars, and character classes, parsed by the `cbm_gitignore_load` function in [`src/discover/gitignore.c`](https://github.com/DeusData/codebase-memory-mcp/blob/main/src/discover/gitignore.c).

### Layer 5: Global Git Excludes

The final layer applies the user-wide exclude file configured via Git's `core.excludesFile` setting. This represents the broadest scope of user-specific ignores and is the only layer that `.cbmignore` negations can potentially override.

## How .cbmignore Rules Are Processed

Understanding the specific behavior of `.cbmignore` requires examining how negations interact with the layered pipeline and directory traversal constraints.

### Root-Only Configuration

Unlike Git ignore files, the codebase-memory-mcp configuration system restricts `.cbmignore` to the repository root only. The implementation in [`src/discover/discover.c`](https://github.com/DeusData/codebase-memory-mcp/blob/main/src/discover/discover.c) lines 1038–1043 explicitly constructs the path as `"%s/.cbmignore"` and loads it once at startup:

```c
char gi_path[PATH_MAX];
snprintf(gi_path, sizeof(gi_path), "%s/.cbmignore", repo_path);
cbm_gitignore_t *cbmignore = cbm_gitignore_load(gi_path);

```

This design choice simplifies precedence calculations but means subdirectories cannot maintain their own `.cbmignore` files.

### Negation Patterns and Precedence

Positive matches (e.g., `generated/`) immediately exclude the path. Negated patterns using `!pattern` can re-include paths, but with critical limitations. As documented in lines 99–104 of [`docs/cbmignore.md`](https://github.com/DeusData/codebase-memory-mcp/blob/main/docs/cbmignore.md), a negation can only rescue a path from the **global-exclude layer (Layer 5)**. It cannot override the built-in skip list, the repository's `.gitignore`, or file suffix/size filters that run earlier in the pipeline.

Within the `.cbmignore` file itself, the last matching rule determines the outcome, as noted in line 36 of the documentation.

### The Parent Directory Constraint

A critical edge case involves directory traversal. If a parent directory is excluded by an earlier rule in the pipeline, the engine never descends into it. Consequently, as documented in lines 95–97, a file cannot be rescued by a negation if its ancestor directory remains excluded. This optimization prevents unnecessary filesystem operations but requires careful ordering of ignore patterns to ensure intended files remain accessible.

## Implementation: Loading and Matching Patterns

The actual implementation bridges the configuration loading and the directory walk. When processing entries in `walk_dir_process_entry`, the system calls `cbm_gitignore_match_result` to evaluate paths against the loaded `.cbmignore` rules.

A negative return value from `cbm_gitignore_match_result` indicates exclusion, while a positive return allows the path. Lines 538–540 of [`src/discover/discover.c`](https://github.com/DeusData/codebase-memory-mcp/blob/main/src/discover/discover.c) demonstrate this logic:

```c
/* In the directory walk */
bool unskipped = cbmignore && !is_safety_core_dir(entry_name) &&
                 cbm_gitignore_match_result(cbmignore, rel_path, true) < 0;
if (unskipped) {
    // path rescued by a .cbmignore negation
}

```

This code demonstrates that even when `.cbmignore` suggests inclusion, the built-in safety check (`is_safety_core_dir`) remains absolute. The `rel_path` parameter provides the repository-relative path for pattern matching against the rules loaded by `cbm_gitignore_load`.

Key source files supporting this functionality include:
- [`docs/cbmignore.md`](https://github.com/DeusData/codebase-memory-mcp/blob/main/docs/cbmignore.md) – Complete syntax specification and precedence rules
- [`src/discover/discover.c`](https://github.com/DeusData/codebase-memory-mcp/blob/main/src/discover/discover.c) – Core discovery logic and pipeline orchestration
- [`src/discover/gitignore.c`](https://github.com/DeusData/codebase-memory-mcp/blob/main/src/discover/gitignore.c) – Pattern parsing and matching implementation
- [`tests/test_discover.c`](https://github.com/DeusData/codebase-memory-mcp/blob/main/tests/test_discover.c) – Validation suite for ignore handling edge cases

## Summary

- The codebase-memory-mcp configuration system implements a **five-layer ignore pipeline** with strict precedence order
- **Built-in exclusions** (Layer 1) are immutable and checked first via `is_safety_core_dir` in [`src/discover/discover.c`](https://github.com/DeusData/codebase-memory-mcp/blob/main/src/discover/discover.c)
- **`.cbmignore`** (Layer 4) supports full gitignore syntax but is only read from the repository root, as implemented in lines 1038–1043
- **Negation patterns** (`!`) can only rescue files from Layer 5 (global Git excludes), not from built-in skips or `.gitignore`
- **Parent directory exclusion** prevents file rescue if ancestors are filtered, optimizing the directory walk according to lines 95–97 of the specification
- The matching logic resides in `cbm_gitignore_match_result`, called during `walk_dir_process_entry` with negative return values indicating exclusion

## Frequently Asked Questions

### Can .cbmignore override .gitignore rules?

No. According to the codebase-memory-mcp source code documentation (lines 99–104), negated patterns in `.cbmignore` cannot rescue paths excluded by the repository's `.gitignore` (Layers 2–3). Negations only affect paths excluded by Layer 5 (Git global excludes). The layered pipeline processes `.gitignore` before `.cbmignore`, and earlier layer rejections are final unless they come from the global exclude file.

### Why isn't my negated pattern working?

Your negated pattern (`!pattern`) likely fails for one of three reasons: it attempts to override the built-in skip list or `.gitignore`, it targets a file whose parent directory remains excluded (preventing directory descent), or it is not the last matching rule in the file. Remember that within `.cbmignore`, the last matching rule wins, and parent exclusions block access to all children regardless of negation patterns.

### Where should I place my .cbmignore file?

Always place `.cbmignore` in the **repository root only**. The discovery engine explicitly loads `%s/.cbmignore` relative to the repository path and ignores nested `.cbmignore` files, as specified in [`docs/cbmignore.md`](https://github.com/DeusData/codebase-memory-mcp/blob/main/docs/cbmignore.md) line 5. Placing the file in subdirectories has no effect on the indexing behavior.

### How do I exclude specific file types?

Add standard gitignore patterns to your `.cbmignore` file in the repository root. For example, use `*.log` to exclude log files or `build/` to exclude directories. These patterns support wildcards, `**` for recursive matching, and character classes. However, note that built-in suffix filters and size limits may run before ignore file evaluation according to the pipeline architecture.