Skip to content

Filtering

Muhammet Şafak edited this page Jul 26, 2026 · 4 revisions

Home / Configuration / Filtering

Filtering

Filtering happens on two independent axes:

  • Which commits are reviewed — --author, --committer, --start-date, --end-date, --text. See Commit filters below.
  • Which files within them — four layers, in order:
    1. Built-in ignore patterns — hardcoded into the binary.
    2. .commitbriefignore — repo-local gitignore-syntax overlay.
    3. --file / --dir path allowlist from the command line.
    4. --exclude-file / --exclude-dir path denylist.

Later layers can override earlier ones (negate patterns in .commitbriefignore, narrow the path scope on the CLI), and the denylist is applied last, so an exclusion always wins.

1. Built-in patterns

Always applied, applied first. The pattern set is hardcoded in the binary at internal/ignore/builtin.go. As of v1.0.0 the list is:

# Lock files
*.lock
package-lock.json
yarn.lock
pnpm-lock.yaml
Cargo.lock
Gemfile.lock
composer.lock
poetry.lock
Pipfile.lock
go.sum

# Vendored / third-party
vendor/**
node_modules/**
Pods/**
third_party/**

# Generated code
*.pb.go
*_pb2.py
*_pb2_grpc.py
*_generated.go
*.gen.go
*.gen.ts
*.gen.js

# Mocks
**/mocks/**
*_mock.go
*.mock.ts

# Build artifacts
dist/**
build/**
target/**
bin/**
out/**
coverage/**
coverage.out
*.test

# CommitBrief cache (avoid recursive review of cached entries)
.commitbrief/cache/**

# Editor / IDE detritus
.idea/**
.vscode/**
*.swp
*~
.DS_Store
Thumbs.db

# Minified / map files
*.min.js
*.min.css
*.map

# Binaries
*.png
*.jpg
*.jpeg
*.gif
*.ico
*.pdf
*.zip
*.tar.gz
*.tgz

These patterns exist for the obvious reasons: lock files churn but have no review value, vendored code is not yours to review, generated artefacts are noise, etc.

2. .commitbriefignore

Drop a .commitbriefignore file at the repo root for project-specific rules. Gitignore syntax — * glob, ** recursive, leading ! to re-include a previously-excluded path.

# Skip our migration files — they are reviewed in the PR description.
db/migrations/**

# But DO review the schema definition (re-include after the bulk exclude).
!db/migrations/schema.sql

# Exclude generated docs.
docs/api/generated/**

A ! re-inclusion can also override a built-in pattern. If your project genuinely needs review on, say, vendor/-style code that is not actually vendored:

!vendor/**

That works because the built-in vendor/** runs first, but the .commitbriefignore layer applies on top and can negate it.

3. --file / --dir path filters

Repeatable CLI flags. Applied after both ignore layers, so they narrow whatever made it past steps 1 and 2.

# Review only these specific files (must already pass the ignore layers).
commitbrief --staged --file src/main.go --file src/util.go

# Review only files under these directories.
commitbrief --unstaged --dir database/seeder --dir app/Models

# Glob patterns: only Go files, anywhere in the diff.
commitbrief --staged --file '*.go'

# Anchored recursive glob: TypeScript anywhere under internal/.
commitbrief --staged --file 'internal/**/*.ts'

# A glob on --dir works the same way.
commitbrief --staged --dir 'app/**'

Behavior:

  • A value with no glob metacharacter (*, ?, [) keeps the original exact behavior:
    • --file matches a file path exactly (the diff's b/ path relative to repo root, or the pre-rename path for a rename).
    • --dir matches any file whose path begins with the given directory plus a slash.
  • A value containing *, ?, or [ is a gitignore-style glob:
    • A pattern with no slash matches the basename at any depth — *.go matches both main.go and internal/cli/root.go.
    • A pattern with a slash is anchored at the repo root — internal/**/*.ts matches .ts files under internal/ only.
    • ** crosses directories; a single * does not cross a /.
    • Character classes work: [abc]*.go.
    • A glob is also tested against a file's pre-rename path, so --file 'legacy/*.go' still catches a file moved out of legacy/.
  • Both can be repeated. Multiple instances are union'd — a file matching any one of them is kept. They combine across kinds, so --file foo.go --dir handlers/ --file '*.md' keeps foo.go, everything under handlers/, and every Markdown file.
  • Use repeated flags, not commas. The flag value is split on commas, so --file '*.go,*.ts' would be parsed as two patterns *.go and *.ts only by accident of the comma split — but a pattern that legitimately needs a comma would break. Always repeat the flag: --file '*.go' --file '*.ts'.
  • An invalid glob (for example an unterminated [) is an error: the run stops before contacting any provider rather than silently filtering out everything.

4. --exclude-file / --exclude-dir path denylists

The inverse of --file / --dir, with identical matching rules — same literal-vs-glob bucketing, same gitignore semantics, same pre-rename path check, same "repeat the flag, don't comma-join" rule, same hard error on an invalid glob. Internally they are one matcher with the keep/drop verdict inverted, so the two can never drift apart.

They are applied after the allowlist, so an exclusion wins:

# Everything under internal/, except internal/cli.
commitbrief --staged --dir internal --exclude-dir internal/cli

# The whole staged diff, minus tests.
commitbrief --staged --exclude-file '*_test.go'

# Repeatable and combinable with everything else.
commitbrief diff main...feature --dir src --exclude-dir src/generated --exclude-file '*.pb.go'

Use these instead of .commitbriefignore when the exclusion is a one-off for this run. .commitbriefignore is for rules the whole team should share on every run.

Commit filters

--author, --committer, --start-date, --end-date and --text select a set of commits rather than a diff. Setting any of them switches the scope from "the index" to a history walk, so they cannot be combined with --staged / --unstaged — neither has commits yet.

# Everything Alice has committed on this branch's history.
commitbrief --author alice

# Either person, since June.
commitbrief --author alice --author bob --start-date 2026-06-01

# A closed window, both ends inclusive.
commitbrief --start-date 2026-03-01 --end-date 2026-03-31

# Message or branch name.
commitbrief --text payment

# Bound the walk to a range instead of all of HEAD's history.
commitbrief diff main..develop --author alice

# Compose freely with the path filters.
commitbrief --author alice --start-date 2026-06-01 \
  --dir internal --exclude-file '*_test.go'
Flag Matches
--author author name or email, case-insensitive substring. Repeatable
--committer committer name or email. Repeatable
--start-date YYYY-MM-DD author date on or after this day, inclusive
--end-date YYYY-MM-DD author date on or before this day, inclusive
--text the commit message, plus branch names (see below)
--max-commits N cap the selection; default 200
--merges keep merge commits, which are excluded by default

Combination rules

AND across kinds, OR within one kind. --author alice --author bob --start-date 2026-06-01 means "(Alice or Bob) and since June".

--merges and --max-commits only shape a commit-filtered review; they never start one. Using either alone is an error rather than a silent no-op.

Date handling

The format is strictly YYYY-MM-DD — not git's "approxidate", which would happily accept next tuesday and quietly resolve a typo. A malformed value, or a --start-date later than the --end-date, is an error before any provider call.

--end-date includes the day you name. Plain git log --until=2026-03-31 stops at that day's midnight and drops it; CommitBrief expands the bound to 23:59:59 local time so the date means what it reads like.

Branch-name matching

When --text is set, CommitBrief also looks for branches (local and remote-tracking) whose name contains the text, and pulls in the commits that exist on such a branch and on no other. So --text payments finds the work on payments/stripe even though none of those commit messages mention payments.

This is best-effort by the nature of git: a squash- or rebase-merged branch no longer owns its commits, so nothing will be found for it. The identity and date filters still apply to whatever the branch contributes.

What the reviewed diff looks like

The selected commits need not be contiguous, so the diff is the concatenation of each commit's patch — not a single cumulative A..B diff, which would drag in the work of every commit in between and make the filter a lie.

Two consequences worth knowing:

  • A file changed in three matching commits appears three times, once per commit. Line numbers in each entry refer to that commit's snapshot.
  • The model sees how the change evolved, not just where it landed. For "review Alice's work this sprint" that is usually what you want.

Where they apply

Command Commit filters
default review, diff, summary, dry-run yes
mcp, guard yes, via tool arguments / inherited flags
commit rejected — it describes the staged index, which has no commits
remote pr rejected — its diff comes from gh pr diff, not local git

Inspection

Use commitbrief dry-run to see exactly how many commits matched and how many files each layer removes:

Commits (walked):  184
Commits (matched): 12
Files (input): 12
  built-in ignore filtered:        3
  .commitbriefignore net filtered: 1
  --file/--dir path filter:        2
  --exclude-file/--exclude-dir:    0
Files (review): 6

The two Commits lines appear only for a commit-filtered run. If the selection hit --max-commits, or the history walk hit its own limit, that is reported too — never silently.

See Dry-run command for the full report.

Pre-send guards (not filters)

Two other layers run before the LLM call but are not filters in the sense above:

  • The .commitbrief/ guard (ADR-0007) prompts/aborts on a diff that touches files under .commitbrief/. It does not silently remove them.
  • The secret scanner looks for credential patterns and prompts/aborts but does not remove flagged lines.

See Secret scanner and Review command §"Pre-send guards" for details.

See also

Clone this wiki locally